当我使用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
它没有足够的信息来执行此操作,并且当它被使用时,它的类型被设置。但是 - 它真的不可能吗?难道它不能从作为第二个参数传递的函数的返回类型推断出类型吗?
如果没有,是否有一些我不知道的更整洁的习语?
答案 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))
在这里,请注意该示例不是零,而是仅用作具有所需类型的事物的示例。