【问题标题】:Why doesn't passing Nil to foldLeft work?为什么不将 Nil 传递给 foldLeft 工作?
【发布时间】:2012-04-04 20:27:28
【问题描述】:

当我使用 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,它没有足够的信息来执行此操作,并且当它被使用时,它的类型已设置。但是 - 它真的不能吗?它不能从作为第二个参数传递的函数的返回类型推断类型吗?

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

【问题讨论】:

  • 遗憾的是,Scala 无法推断出这种情况的类型。让我们希望将来会对此进行修改。
  • 在实践中应该问题不大。如果您只是处理一些特定的类,请在顶部 type S = (SomethingClass, SomethingElseClass)val nil = List.empty[(SomethingClass, SomethingElseClass)] 处创建一个类型别名。如果您正在编写泛型方法,则您的类型将已经有短别名,例如 TU

标签: scala type-inference fold


【解决方案1】:

Scalas 类型推断引擎从左到右工作 - 因此 scalac 无法推断 foldLeft 的第一个参数列表的正确类型。你必须给编译器一个提示使用什么类型。您可以使用List.empty[TYPE],而不是使用List[TYPE]()

(List(1,2,3) foldLeft List.empty[Int]) { (x,y) =>  y :: x }
(List.empty[Int] /: List(1, 2, 3)) { (x,y) => y :: x }

【讨论】:

  • 所以没有更短的方法可以解决这个问题?例如,当它是一个元组列表时,它开始看起来很荒谬:list.foldLeft(List.empty[(SomethingClass, SomethingElseClass)]) { (x,y) =&gt; y :: x }。它占据了一整行并且掩盖了您实际尝试做的事情的含义!
  • @Russell:不,没有更短的方法。您唯一能做的就是定义一个类型别名,当您多次出现“long”类型时,该别名很有用。
  • 感谢您的回答,因为我不能同时接受两者,所以我会接受一个并支持另一个。
【解决方案2】:

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))

这里,请注意示例不是零,而是仅用作具有您想要的类型的事物的示例。

【讨论】:

  • 感谢您的回答,因为我不能同时接受两者,所以我会接受一个并支持另一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-07
  • 2015-09-09
  • 2017-02-20
  • 1970-01-01
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多