为什么不通过Nil到foldLeft工作?

时间:2012-03-20 11:12:19

标签: scala type-inference fold

当我使用foldLeft构建列表时,我经常因为必须明确键入注入的参数而感到恼火,并希望我可以使用“Nil”代替 - 这是一个人为的例子:

scala> List(1,2,3).foldLeft(List[Int]())((x,y) =>  y :: x)
res17: List[Int] = List(3, 2, 1)

scala> List(1,2,3).foldLeft(Nil)((x, y) => y :: x)
<console>:10: error: type mismatch;
 found   : List[Int]
 required: scala.collection.immutable.Nil.type
              List(1,2,3).foldLeft(Nil)((x,y) =>  y :: x)

这对于List[Int]来说并不是那么糟糕,但是一旦你开始使用你自己的类的列表,这几乎肯定会有更长的名字,甚至是元组或其他容器的列表,所以那里你需要指定多个类名,它会变得可怕:

list.foldLeft(List.empty[(SomethingClass, SomethingElseClass)]) { (x,y) => y :: x }

我猜测它不起作用的原因是,与5 :: Nil之类的东西相比,编译器可以将空列表的类型推断为List[Int],但是Nil作为参数传递给foldLeft它没有足够的信息来执行此操作,并且当它被使用时,它的类型被设置。但是 - 它真的不可能吗?难道它不能从作为第二个参数传递的函数的返回类型推断出类型吗?

如果没有,是否有一些我不知道的更整洁的习语?

2 个答案:

答案 0 :(得分:14)

Scalas类型推理引擎从左到右工作 - 因此scalac无法推断foldLeft的第一个参数列表的正确类型。您必须为编译器提供一个提示,使用哪种类型。您可以使用List[TYPE]()

,而不是使用List.empty[TYPE]
(List(1,2,3) foldLeft List.empty[Int]) { (x,y) =>  y :: x }
(List.empty[Int] /: List(1, 2, 3)) { (x,y) => y :: x }

答案 1 :(得分:12)

Scala的类型推断一次只能处理一个参数块。没有任何其他上下文的Nil没有特定类型,因此选择了最具限制性的类型(Nothing)。

它也无法推断函数的返回类型,因为返回类型取决于Nil的类型。一般来说,摆脱这种循环是很棘手的(如果你没有说明你的意思)。

但是,您可以应用一些技巧来减少麻烦。

首先,fold的类型签名只有集合的类型,所以如果你可以使用那种折叠,你可以解决问题。例如,您可以编写自己的反向展平:

List(List(1),List(2),List(3)).fold(Nil)( (x,y) => y ::: x )

其次,如果您正在创建相同类型的新集合,则使用现有集合生成空集合通常比尝试插入空集合的类型更容易。如果你在某处定义了管道,那就特别容易了:

class PipeAnything[A](a: A) { def |>[B](f: A => B) = f(a) }
implicit def anything_can_be_piped[A](a: A) = new PipeAnything(a)

List(1,2,3) |> { x => x.foldLeft(x.take(0))( (y,z) => z :: y ) }

最后,不要忘记你可以非常轻松地定义自己的方法,可以做一些与你的类型一致的东西,即使你必须使用一些技巧来使它工作:

def foldMe[A,B](example: A, list: List[B])(f: (List[A],B) => List[A]) = {
  (List(example).take(0) /: list)(f)
}

scala> foldMe( ("",0), List("fish","wish","dish") )( (x,y) => (y.take(1), y.length) :: x )
res40: List[(java.lang.String, Int)] = List((d,4), (w,4), (f,4))

在这里,请注意该示例不是零,而是仅用作具有所需类型的事物的示例。

相关问题