在Scala中使用foldLeft将参数列表应用于curried函数

时间:2011-09-30 06:14:31

标签: scala currying hlist

是否可以在参数列表上执行foldLeft,其中提供给折叠的初始值是完全curried函数,运算符是apply,列表是要传递给函数f的参数?

例如,假设f定义为:

scala> val f = (i: Int, j: Int, k: Int, l: Int) => i+j+k+l
f: (Int, Int, Int, Int) => Int = <function4>

我们当然可以直接使用:

scala> f(1, 2, 3, 4)
res1: Int = 10

或者咖喱并一次应用一个参数:

scala> f.curried
res2: Int => Int => Int => Int => Int = <function1>

scala> f.curried.apply(1).apply(2).apply(3).apply(4)
res3: Int = 10

乍一看,这似乎是foldLeft的工作。

我首次尝试使用apply描述此foldLeft序列,如下所示:

scala> List(1, 2, 3, 4).foldLeft(f.curried)({ (g, x) => g.apply(x) })

然而,这会产生以下错误:

<console>:9: error: type mismatch;
 found   : Int => Int => Int => Int
 required: Int => Int => Int => Int => Int
              List(1, 2, 3, 4).foldLeft(f.curried)({ (g, x) => g.apply(x) })

我对错误消息的阅读是类型推断需要g的一些提示。

我正在寻找的解决方案除了g的类型外,在原始表达式中保留所有未修改的内容:

List(1, 2, 3, 4).foldLeft(f.curried)({ (g: ANSWER, x) => g.apply(x) })

我的第一个想法是联盟类型在这里很有用。我已经看过Miles Sabin使用Curry-Howard推导联合类型,所以如果第一次预感是真的,那么我似乎有解决问题所需的基本机制。

然而:即使联合类型是答案,如果我可以引用“从完全curried类型的函数到curried函数的类型的所有类型的联合,除了提供的最后一个参数之外的所有类型的联合”将是有用的。 。换句话说,一种转换类型的方法:

T1 => ... => Tn

进入联合类型:

(T1 => ... => Tn) |∨| ... |∨| (Tn-1 => Tn)

可用作上面g的类型。

foldLeft执行List会将讨论限制为T1Tn-1完全相同的情况。像

这样的符号
(T1 =>)+ Tn

将描述我想为g提供的类型。

我要问的具体情况不需要任意长链,所以我们可以使用

在迭代器上提供边界
(T1 =>){1,4} Tn

展望未来想要为不相等的类型的链做到这一点,或许对类型的一些神奇函数将链条切割成所有后缀的集合更有用:

Suffixes(T1 => ... => Tn)

目前实现这一点远远超出了我的Scala能力。任何关于如何这样做的提示将不胜感激。是否可以通过高级使用Scala的现有类型系统或通过编译器插件来完成,我不知道。

正如下面的评论中所指出的,将结果称为“联合类型”并不适合此用例。我不知道还有什么可以称之为,但这是我目前最接近的想法。其他语言是否对这个想法有特别的支持?如何在Coq和Agda中工作?

命名这个问题并理解它在大局(类型理论,可判定性等等)方面的位置对我来说比ANSWER的工作实现更重要,尽管两者都很好。奖励指向任何能够与Scalaz,Monoids或类别理论建立联系的人。

3 个答案:

答案 0 :(得分:28)

事实证明这比我最初预期的要简单得多。

首先,我们需要定义一个简单的HList

sealed trait HList

final case class HCons[H, T <: HList](head : H, tail : T) extends HList {
  def ::[H1](h : H1) = HCons(h, this)
  override def toString = head+" :: "+tail.toString
}

trait HNil extends HList {
  def ::[H1](h : H1) = HCons(h, this)
  override def toString = "HNil"
}

case object HNil extends HNil
type ::[H, T <: HList] = HCons[H, T]

然后我们可以借助类型类

来归纳地定义类似折叠的函数
trait FoldCurry[L <: HList, F, Out] {
  def apply(l : L, f : F) : Out
}

// Base case for HLists of length one
implicit def foldCurry1[H, Out] = new FoldCurry[H :: HNil, H => Out, Out] {
  def apply(l : H :: HNil, f : H => Out) = f(l.head)
}

// Case for HLists of length n+1
implicit def foldCurry2[H, T <: HList, FT, Out]
  (implicit fct : FoldCurry[T, FT, Out]) = new FoldCurry[H :: T, H => FT, Out] {
    def apply(l : H :: T, f : H => FT) = fct(l.tail, f(l.head))
}

// Public interface ... implemented in terms of type class and instances above
def foldCurry[L <: HList, F, Out](l : L, f : F)
  (implicit fc : FoldCurry[L, F, Out]) : Out = fc(l, f)

我们可以像这样使用它,首先是你原来的例子,

val f1 = (i : Int, j : Int, k : Int, l : Int) => i+j+k+l
val f1c = f1.curried

val l1 = 1 :: 2 :: 3 :: 4 :: HNil

// In the REPL ... note the inferred result type
scala> foldCurry(l1, f1c)
res0: Int = 10

对于具有不同arity和非统一参数类型的函数,我们也可以使用相同的未修改foldCurry

val f2 = (i : Int, s : String, d : Double) => (i+1, s.length, d*2)
val f2c = f2.curried

val l2 = 23 :: "foo" :: 2.0 :: HNil

// In the REPL ... again, note the inferred result type
scala> foldCurry(l2, f2c)
res1: (Int, Int, Double) = (24,3,4.0)

答案 1 :(得分:6)

您的函数正好需要4 Int个参数。 foldLeft是一个适用于任意数量元素的函数。您提到List(1,2,3,4),但如果您有List(1,2,3,4,5)List()

,该怎么办?

List.foldLeft[B]还希望函数返回相同的类型B,但在您的情况下Int和某些Function1[Int, _]的类型不同。

无论你想出什么样的解决方案,都不会是一般性的。例如,如果您的函数类型为(Int, Float, Int, String) => Int,该怎么办?然后,您需要List[Any]

因此List.foldLeft的作业肯定是

考虑到这一点(警告非scala代码):

class Acc[T](f: Function1[T, _]) {
  private[this] var ff: Any = f
  def apply(t: T): this.type = {
    ff = ff.asInstanceOf[Function1[T,_]](t)
    this
  }
  def get = ff match { 
    case _: Function1[_,_] => sys.error("not enough arguments")
    case res => res.asInstanceOf[T]
  }
}

List(1,2,3,4).foldLeft(new Acc(f.curried))((acc, i) => acc(i)).get
// res10: Int = 10

答案 2 :(得分:3)

好的没有scalaz,也没有解决方案,但有一个解释。如果你使用带有1的f.curried.apply然后在REPL中使用2个参数,则观察返回结果类型DO实际上每次都不同! FoldLeft非常简单。它的类型是固定的,你的起始参数是f.curried,因为它与f.curried.apply(1)没有相同的签名,它不起作用。 所以起始参数和结果必须是同一类型。类型必须与foldLeft的起始和元素保持一致。你的结果甚至是Int,所以绝对不行。希望这会有所帮助。