Суммирование списка опций с аппликативными функторами - PullRequest
9 голосов
/ 16 ноября 2011

У меня есть список [Option [Int]], и я хочу суммировать его, используя аппликативные функторы.Из [1] я понимаю, что это должно быть что-то вроде следующего

import scalaz._
import Scalaz._

List(1,2,3).map(some(_)).foldLeft(some(0))({
    case (acc,value) => (acc <|*|> value){_+_}
})

, однако я просто не могу найти правильный способ написать это.Я был бы рад, если бы кто-нибудь мог помочь мне с этим.

Большое спасибо

[1] Как объединить значения параметров в Scala?

Редактировать

Спасибо за все великолепные ответы.

Если в списке есть None, я хочу, чтобы он возвратил None.Я пытаюсь заменить Null / Exception на Option / Either и посмотреть, смогу ли я создать какой-нибудь полезный код.

Некоторая функция заполнит мой список, и я хочу обработать его как можно проще, не проверяя, был ли один из элементов None.Он должен работать так же, как Исключения, когда мне не нужно проверять это в моей функции, но пусть вызывающий позаботится об этом.

Ответы [ 6 ]

15 голосов
/ 16 ноября 2011

Вам не нужен Скалаз для этого. Вы можете просто сгладить список, который преобразует его в List[Int], удалив все элементы, которые были None. Тогда вы можете уменьшить его:

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int
9 голосов
/ 16 ноября 2011

Если у вас есть Option[T] и если есть Monoid для T, то есть Monoid[Option[T]]:

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
  val monoid = implicitly[Monoid[T]]
  val zero = None
  def append(o1: Option[T], o2: =>Option[T]) = (o1, o2) match {
    case (Some(a), Some(b)) => Some(monoid.append(a, b))
    case (Some(a), _)       => o1
    case (_, Some(b))       => o2
    case _                  => zero
  }
}

Как только вы наденете это, вы можете просто использовать sum (лучше, чем foldMap(identity), как предложено @missingfaktor):

 List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6)

UPDATE

На самом деле мы можем использовать аппликативы для упрощения кода выше:

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
   val monoid = implicitly[Monoid[T]]
   val zero = None
   def append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
}

, что заставляет меня думать, что мы можем даже обобщить следующее:

implicit def applicativeOfMonoidIsMonoid[F[_] : Applicative, T : Monoid]: Monoid[F[T]] = 
  new Monoid[F[T]] {
    val applic = implicitly[Applicative[F]]
    val monoid = implicitly[Monoid[T]]

    val zero = applic.point(monoid.zero)
    def append(o1: F[T], o2: =>F[T]) = (o1 |@| o2)(monoid.append(_, _))
  }

Таким образом, вы могли бы даже суммировать списки списков, списки деревьев, ...

UPDATE2

Уточнение вопроса заставляет меня понять, что ОБНОВЛЕНИЕ выше неверно!

Прежде всего, optionTIsMonoid, в пересмотренном виде, не эквивалентен первому определению, поскольку первое определение пропустит значения None, а второе вернет None, как только в 10 * будет * список ввода. Но в этом случае это не Monoid! Действительно, Monoid[T] должен уважать законы Моноида, а zero должен быть элементом идентичности .

У нас должно быть:

zero    |+| Some(a) = Some(a)
Some(a) |+| zero    = Some(a)

Но когда я предложил определение для Monoid[Option[T]] с использованием Applicative для Option, это был не тот случай:

None    |+| Some(a) = None
None    |+| None    = None
=> zero |+| a      != a

Some(a) |+| None    = zero
None    |+| None    = zero
=> a    |+| zero   != a

Исправить несложно, нам нужно изменить определение zero:

// the definition is renamed for clarity
implicit def optionTIsFailFastMonoid[T : Monoid]: Monoid[Option[T]] = 
  new Monoid[Option[T]] {
    monoid = implicitly[Monoid[T]]
    val zero = Some(monoid.zero)
    append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
  }

В этом случае мы будем иметь (с T как Int):

Some(0) |+| Some(i) = Some(i)
Some(0) |+| None    = None
=> zero |+| a       = a

Some(i) |+| Some(0) = Some(i)
None    |+| Some(0) = None
=> a    |+| zero    = zero

Это доказывает, что закон об идентичности проверен (мы также должны убедиться, что ассоциативный закон соблюдается, ...).

Теперь у нас есть 2 Monoid[Option[T]], которые мы можем использовать по желанию, в зависимости от поведения, которое мы хотим при суммировании списка: пропуск None s или «быстрый провал».

6 голосов
/ 16 ноября 2011
scala> List(1, 2, 3).map(some).foldLeft(0 some) {
     |   case (r, c) => (r |@| c)(_ + _)
     | }
res180: Option[Int] = Some(6)
5 голосов
/ 16 ноября 2011

Один из вариантов - сначала упорядочить весь список, а затем сложить как обычный:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_))
a.sequence map (_.foldLeft(0)(_+_))
0 голосов
/ 07 сентября 2016

Нашел это где-то некоторое время назад, больше не могу найти источник, но он работал для меня

 def sumOpt1(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, None) => None
          case (None, Some(el)) => Some(el)
          case (Some(p), None) => Some(p)
          case (Some(p), Some(el)) => Some(p + el)
        }
    }
  }

или

 def sumOpt2(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, None) => None
          case (p, el) => Some(p.getOrElse(0) + el.getOrElse(0))
        }
    }
  }

или

def sumOpt3(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, el) => el
          case (p, None) => p
          case (Some(p), Some(el)) => Some(p + el)
        }
    }
  }
0 голосов
/ 26 ноября 2011

С Scalaz ApplicativeBuilder будет другой вариант.

import scalaz._
import Scalaz._

List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join
...