2013-07-02 25 views
7

Tôi đang sử dụng EitherT của Scalaz 7 để xây dựng cho sự hiểu biết pha trộn trạng thái và \ /. Càng xa càng tốt; Tôi nhận được nội dung cơ bản:Cách trả lại một tuple bên trong EitherT

State[MyStateType, MyLeftType \/ MyRightType] 

và cho phép tôi xây dựng hiểu thấu có biến số đẹp ở bên trái của < -.

Nhưng tôi không thể tìm ra cách trả về bộ dữ liệu từ một hành động của tiểu bang. Kết quả duy nhất chỉ là tốt - trong đoạn code dưới đây, "val comprehension" là chính xác những gì tôi muốn xảy ra.

Nhưng mọi thứ sẽ sụp đổ khi tôi muốn trả lại một bộ dữ liệu; "val otherComprehension" sẽ không cho phép tôi làm

(a, b) <- comprehension 

Có vẻ như nó dự kiến ​​bên trái của \/là một Monoid và tôi không hiểu tại sao. Tôi đang thiếu gì?

(Scalaz 7 2.0.0-THÔNG SỐ CHUNG, Scala 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) 
} 

Edit: Tôi đã thêm bằng chứng cho thấy MyLeftType là một đơn nguyên, mặc dù nó không phải. Trong mã thật của tôi, MyLeftType là một lớp trường hợp (gọi tắt là EarlyReturn), vì vậy tôi có thể cung cấp một số không, nhưng thêm chỉ hoạt động nếu một trong các đối số là một số không:

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 \/""") 
     } 
    } 

Tôi không thuyết phục đây là một ý tưởng hay, nhưng nó giải quyết được vấn đề.

+1

Có điều gì đó kỳ lạ xảy ra theo cách thức 2.10.1+ xóa bỏ 'for'-comprehension ở đây — xem [câu hỏi này] (http://stackoverflow.com/q/17424763/334519) cho một phiên bản đơn giản của cùng một vấn đề. –

+2

Và để rõ ràng, điều này xảy ra vì việc lọc 'EitherT' (hoặc' \/') yêu cầu một cá thể monoid ở phía bên trái, và vì lý do nào đó, 2.10.2 đang gắn một thao tác lọc trong' 'này. –

Trả lời

4

Như tôi đã lưu ý trong một chú thích ở trên, vấn đề là phiên bản thứ hai của khử đường for -comprehension của bạn liên quan đến một hoạt động lọc trong 2.10.2 (và 2.10.1, nhưng không 2.10.0), và không thể lọc EitherT (hoặc đồng bằng cũ \/) mà không có một ví dụ monoid cho loại ở phía bên trái.

Nó khá dễ dàng để xem lý do tại sao monoid là cần thiết trong ví dụ sau:

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

y là gì? Rõ ràng rằng nó phải là một loại "trống" String \/ Int và vì \/ là đúng thiên vị, chúng tôi biết rằng đó không phải là giá trị ở bên đó. Vì vậy, chúng ta cần một số không cho phía bên trái, và dụ monoid cho String cung cấp này-nó chỉ là chuỗi rỗng:

assert(y == "".left) 

Theo this answer-my related question về mẫu tuple trong for -comprehensions, hành vi bạn nhìn thấy trong 2.10.2 là chính xác và dự định - cuộc gọi dường như hoàn toàn không cần thiết đến withFilter là ở đây để ở.

Bạn có thể sử dụng workaround trong câu trả lời Petr Pudlák, nhưng nó cũng đáng chú ý là phiên bản đường miễn phí sau đây cũng khá rõ ràng và súc tích:

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

này được nhiều hơn hoặc ít hơn những gì tôi sẽ ngây thơ mong đợi các for-comprehension desugar để, anyway (và tôi là not the only one).

2

Mà không biết nguyên nhân, tôi tìm thấy một workaround thể:

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

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

hoặc có lẽ tốt hơn một chút

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

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

Đó là không phải là rất tốt đẹp, nhưng không được công việc.

(Tôi thực sự đánh giá cao mà bạn đã thực hiện một khép kín, ví dụ làm việc của vấn đề.)

+1

Thay vì x = p._1; y = p._2; bạn chỉ có thể làm (x, y) = p. Cảm thấy lạ rằng bạn cần phải làm điều đó trên một dòng khác (liên kết và bình luận từ @TravisBrown có thông tin hữu ích). –

+0

@JamesMoore Đúng, đã sửa. –

Các vấn đề liên quan