Как вы можете рекурсивно построить список внутри особой черты Some в Scala? - PullRequest
0 голосов
/ 25 августа 2018

Я сейчас прохожу красную книгу «Функциональное программирование в Scala», поэтому в то же время я изучаю Scala. Если я правильно понимаю, черта не означает объект. Поправь меня, если я здесь не прав.

Моя проблема в том, что я не знаю, как создать список типа A, в то время как он заключен в черту Some. Буду признателен за подсказки в правильном направлении.

В упражнении, которое я выполняю, меня просят определить функцию, которая должна преобразовывать каждый элемент в списке, а затем иметь весь список внутри черты Some.

вот мой код:

def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match {
    case Nil => Nil: B
    case h :: t => f(h) flatMap ( hh => hh :: traverse(t)(f))
  }

Мне кажется, что я на правильном пути, но интерпретатор scala жалуется, что :: недоступен для Option[List[B]]. Я думаю, это потому, что сигнатура функции не возвращает список, она возвращает список, заключенный в Some.

Но может ли моя интуиция насчет flatMap также ошибаться? f(h) возвращает Option[B]. Вызов flatmap на самом деле выглядит внутри Option, поэтому hh имеет тип B правильно? Моя логика заключается в том, что таким образом я могу создать список типа B с помощью функции hh :: traverse(t)(f). Но я не совсем уверен в том, что я здесь.

Если это имеет какое-то значение, я использую интерпретатор scala с командой: paste. Я не уверен, что из-за этого дела идут плохо.

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Еще немного о монадических операциях. Option - это монада, но может быть проще рассматривать ее как контейнер с нулем или одним значением. Что-то вроде List(), у которого нет элементов, или List(x), у которого ровно один элемент. Чтобы изменить элемент, скрытый внутри вашего контейнера, вам нужно map:

List(1).map(_ + 1) => List(2)
Some(1).map(_ + 1) => Some(2)

Но map не может изменить количество элементов в списке. Так что вам нужна еще одна операция, которая называется flatMap, которая принимает функцию, которая в свою очередь принимает элемент и возвращает List:

List(1).flatMap { x => List() } => List()
Some(1).flatMap { x => None } => None

List(1).flatMap { x => List(x + 1) } => List(2)
Some(1).flatMap { x => Some(x + 1) } => Some(2)

Это распространит «сбой», который является пустым списком, так как List().flatMap { ...whatever...} всегда будет возвращать пустой список.

traverse делает именно это, распространяя «сбой», если ваша функция f для любого элемента возвращает None (т.е. List() в моей интерпретации) и объединяя результаты без сбоев в новый список внутри контейнера. Поскольку то, что вы получаете из traverse, является контейнером со списком внутри, вам нужно применить map. И потому что «сбой», если это произойдет, должен распространяться, вам нужно flatMap.

0 голосов
/ 25 августа 2018

Если я правильно понимаю, черта не означает объект.

Черты - это черты, объекты - это объекты. Черты похожи на «интерфейсы» в Java, а object - это одноэлементные объекты. Эти одноэлементные объекты могут выходить из черт.

Some черта

Some - это не черта, это расширение класса Option.

возвращает список, завернутый в Some.

Возвращает список, заключенный в Option (т.е. это может быть None, без какого-либо списка). Чтобы вызвать :: для необязательного значения списка, вам потребуется еще один map:

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => f(h) flatMap {
    hValue => traverse(t)(f) map {
      tValue => hValue :: tValue
    }
  }
}

который вы можете обозначить как

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => f(h) flatMap {
    hValue => traverse(t)(f) map (hValue :: _)
  }
}

или просто используйте for -разбор сразу:

def traverse[A, B](a: List[A])(f: A => Option[B])
: Option[List[B]] = a match {
  case Nil => Some(Nil)
  case h :: t => for {
    hValue <- f(h)
    tValue <- traverse(t)(f)
  } yield (hValue :: tValue)
}
...