Почему передача Nil на foldLeft не работает?
Когда я строю список, используя 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 ответа
Механизм логического вывода типа 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 }
Вывод типа 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))
Здесь обратите внимание, что пример не ноль, а скорее используется только как пример вещи с нужным вам типом.