Почему передача Nil на foldLeft не работает? - PullRequest
14 голосов
/ 20 марта 2012

Когда я строю список, используя 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 ]

14 голосов
/ 20 марта 2012

Механизм логического вывода типа 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 }
12 голосов
/ 20 марта 2012

Вывод типа 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))

Здесь, обратите внимание, что пример не ноль , а скорее используется только как пример вещи с нужным вам типом.

...