Подтвердить что ты не робот

Почему передача 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 у него недостаточно информации для этого, и к тому времени, когда он округляется до использования, его тип установлен. Но... это правда, что это невозможно? Не может ли он вывести тип из возвращаемого типа функции, переданной в качестве второго аргумента?

А если нет, есть ли какая-то более аккуратная идиома, о которой я просто не знаю?

4b9b3361

Ответ 1

Механизм вывода типов 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 }

Ответ 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))

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