2011-11-16 16 views
9

我有一個List [Option [Int]]並且想要使用applicative函數對它進行求和。 從[1]據我所知,它應該像下面使用Applicative Functors來總結一個選項列表

import scalaz._ 
import Scalaz._ 

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

但我根本無法弄清楚寫這個的正確方法。 如果有人能幫助我,我會很高興。

非常感謝您

[1] How to combine Option values in Scala?

編輯

感謝所有偉大的答案。

如果列表中有任何無,我希望它返回無。 我想用Option/Either替換Null/Exception,看看我能否生成一些可用的代碼。

某些函數會填充我的列表,我想盡可能簡單地處理它,而不檢查其中一個元素是否爲None。它的工作原理與Exceptions類似,我不必在函數中檢查它,但讓調用者處理它。

+0

嗨曼努埃爾!是的非常重要的部分總是如何處理無:忽略或失敗快速看到這裏的一個相關的例子:https://gist.github.com/970717 – AndreasScheinert

+0

嗨安德烈亞斯。像你的代碼片段是我需要的。 –

回答

9

如果你有Option[T],如果有一個爲T一個Monoid,然後有一個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

實際上,我們可以使用applicatives簡化上面的代碼:

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

問題的澄清使我認識到,上面的UPDATE是不正確!

首先optionTIsMonoid的,因爲重構,是不是等同於第一個定義,因爲第一個定義將跳過None值,而第二個將盡快返回None,因爲是在輸入列表中的None。但在這種情況下,這不是Monoid!事實上,Monoid[T]必須尊重Monoid法律,而zero必須是identity元素。

我們應該有:

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

但是,當我使用ApplicativeOption提出的定義爲Monoid[Option[T]],這種情況並非如此:

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(_, _)) 
    } 

在這種情況下,我們將(與TInt):

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 

這證明法律身份驗證(我們也應該驗證associative法律得到尊重,...)。

現在我們有2 Monoid[Option[T]],我們可以隨意使用,這取決於我們在總結列表時想要的行爲:跳過None s或「快速失敗」。

+0

在發佈之後,我意識到我沒有真正回答這個問題,因爲我沒有使用任何應用程序。只要把它當作衆多選擇之一,... – Eric

+0

我不明白這是如何工作的...爲什麼在任何地方都沒有'+'? – Owen

+1

'.foldMap(identity)'可以替換爲'.asMA.sum'。 – missingfaktor

5

一種選擇將是第一個序列的整個列表,然後把它摺疊像普通:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_)) 
a.sequence map (_.foldLeft(0)(_+_)) 
+2

或者的確,只是a.sequence地圖{_sum} – Submonoid

14

你並不真的需要Scalaz這一點。您可以將該列表弄平,然後將其轉換爲List[Int],刪除所有None的項目。然後你就可以減少碳排放量

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int 
+0

我已經將它解釋爲想讓結果爲'None',如果有'None',但現在你提到了,我不確定我是對... – Owen

+0

非常好的一點,我想OP應該指定他是否希望總和失敗,如果至少有一個項目是None,或者總結具有值的項目,忽略Nones。 –

+6

或'.flatten.sum'。 – missingfaktor

6
scala> List(1, 2, 3).map(some).foldLeft(0 some) { 
    | case (r, c) => (r |@| c)(_ + _) 
    | } 
res180: Option[Int] = Some(6) 
0

隨着斯卡拉的ApplicativeBuilder將是另一種選擇。

import scalaz._ 
import Scalaz._ 

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

發現這個地方前一陣子,不能再找到源,但它一直致力於爲我

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) 
     } 
    } 
    } 
相關問題