2013-07-02 54 views
7

我正在使用Scalaz 7的EitherT來構造將狀態和\ /混合起來的理解。到現在爲止還挺好;我得到的東西,基本上就是:如何返回EitherT中的元組

State[MyStateType, MyLeftType \/ MyRightType] 

和,讓我建立-內涵是對<左側漂亮的變量 - 。我想不出如何從狀態動作返回元組。單個結果就好 - 在下面的代碼中,「val理解」正是我想要發生的事情。

但是當我想返回一個元組時,事情就會崩潰; 「VAL otherComprehension」不會讓我做

(a, b) <- comprehension 

它看起來像它預計\左側/是一個Monoid,我不明白爲什麼。我錯過了什麼?

(Scalaz 7 2.0.0-SNAPSHOT,斯卡拉2.10.2)

object StateProblem { 
    case class MyStateType 
    case class MyRightType 
    case class MyLeftType 

    type StateWithFixedStateType[+A] = State[MyStateType, A] 
    type EitherTWithFailureType[F[+_], A] = EitherT[F, MyLeftType, A] 
    type CombinedStateAndFailure[A] = EitherTWithFailureType[StateWithFixedStateType, A] 

    def doSomething: CombinedStateAndFailure[MyRightType] = { 
    val x = State[MyStateType, MyLeftType \/ MyRightType] { 
     case s => (s, MyRightType().right) 
    } 
    EitherT[StateWithFixedStateType, MyLeftType, MyRightType](x) 
    } 

    val comprehension = for { 
    a <- doSomething 
    b <- doSomething 
    } yield (a, b) 

    val otherComprehension = for { 
    // this gets a compile error: 
    // could not find implicit value for parameter M: scalaz.Monoid[com.seattleglassware.StateProblem.MyLeftType] 
    (x, y) <- comprehension 

    z <- doSomething 
    } yield (x, y, z) 
} 

編輯:我補充證據MyLeftType是一個單子,即使它不是。在我真正的代碼,MyLeftType是一個案例類(稱爲EarlyReturn),所以我可以提供一個零,但只追加作品,如果其中一個參數是零:

implicit val partialMonoidForEarlyReturn = new Monoid[EarlyReturn] { 
    case object NoOp extends EarlyReturn 
    def zero = NoOp 
    def append(a: EarlyReturn, b: => EarlyReturn) = 
     (a, b) match { 
     case (NoOp, b) => b 
     case (a, NoOp) => a 
     case _   => throw new RuntimeException("""this isnt really a Monoid, I just want to use it on the left side of a \/""") 
     } 
    } 

我不相信這是一個好主意,但它解決了這個問題。

+1

有一些奇怪的事情正在進行2.10.1+在這裏解釋'for'-comprehension-看到[這個問題](http://stackoverflow.com/q/17424763/334519)的簡化版同樣的問題。 –

+2

而且很清楚,這是因爲過濾'EitherT'(或'\ /')需要左側的monoid實例,出於某種原因,2.10.2將過濾操作粘在這個'for'''理解中。 –

回答

4

正如我上面評論指出,問題是你的第二個for -comprehension的脫糖版本涉及在2.10.2(和2.10.1,但不是2.10.0)中進行過濾操作,並且不可能在沒有左側類型的monoid實例的情況下過濾EitherT(或普通舊\/)。

這是很容易明白爲什麼獨異是必要的,下面的例子:

val x: String \/ Int = 1.right 
val y: String \/ Int = x.filter(_ < 0) 

什麼是y?很明顯,它必須是某種「空」String \/ Int,並且由於\/是正確的偏見,我們知道它不能成爲這方面的價值。因此,我們需要在左側的零和String的幺實例提供此,它只是一個空字符串:

assert(y == "".left) 

根據this answermy related question關於元組模式在for -comprehensions,你的行爲在2.10.2中看到的是正確的和意圖的 - 對withFilter的顯然完全不必要的呼叫在這裏停留。

您可以使用切赫Pudlák的答案的解決辦法,但它也是值得注意的是,以下的無糖版本也很清晰,簡明:

val notAnotherComprehension = comprehension.flatMap { 
    case (x, y) => doSomething.map((x, y, _)) 
} 

這是多了還是少了什麼我會天真地期待無論如何(並且我是not the only one),for-desprear解除。

2

不知道的原因,我發現了一個可能的解決方法:

for { 
    //(x, y) <- comprehension 
    p <- comprehension 

    z <- doSomething 
} yield (p._1, p._2, z) 

或許略勝一籌

for { 
    //(x, y) <- comprehension 
    p <- comprehension 
    (x, y) = p 

    z <- doSomething 
} yield (x, y, z) 

這不是很漂亮,但做這項工作。

(我真的很感謝你對這一問題進行一個自包含的,工作示例)。

+1

而不是x = p._1; y = p._2;你可以做(​​x,y)= p。感覺很奇怪,你需要在另一行上做(鏈接和評論@ TravisBrown有有用的信息)。 –

+0

@JamesMoore確實,已更正。 –