2013-01-17 24 views
33

Tôi có hai hàm trả về Tương lai. Tôi đang cố gắng để nuôi một kết quả sửa đổi từ chức năng đầu tiên vào khác bằng cách sử dụng một hiểu năng suất.Tương lai [Tùy chọn] trong Scala cho thấu hiểu

Cách tiếp cận này hoạt động:

val schoolFuture = for { 
    ud <- userStore.getUserDetails(user.userId) 
    sid = ud.right.toOption.flatMap(_.schoolId) 
    s <- schoolStore.getSchool(sid.get) if sid.isDefined 
    } yield s 

Tuy nhiên tôi không hài lòng với việc có "nếu" trong đó, có vẻ như rằng tôi sẽ có thể sử dụng bản đồ để thay thế.

Nhưng khi tôi cố gắng với một bản đồ:

val schoolFuture: Future[Option[School]] = for { 
    ud <- userStore.getUserDetails(user.userId) 
    sid = ud.right.toOption.flatMap(_.schoolId) 
    s <- sid.map(schoolStore.getSchool(_)) 
    } yield s 

tôi nhận được một lỗi biên dịch:

[error] found : Option[scala.concurrent.Future[Option[School]]] 
[error] required: scala.concurrent.Future[Option[School]] 
[error]   s <- sid.map(schoolStore.getSchool(_)) 

Tôi đã chơi xung quanh với một vài biến thể, nhưng đã không tìm thấy bất cứ điều gì hấp dẫn công trinh. Bất cứ ai có thể đề nghị một hiểu tốt hơn và/hoặc giải thích những gì sai với ví dụ thứ 2 của tôi?

Dưới đây là một ví dụ Runnable tối thiểu nhưng hoàn chỉnh với Scala 2.10:

import concurrent.{Future, Promise} 

case class User(userId: Int) 
case class UserDetails(userId: Int, schoolId: Option[Int]) 
case class School(schoolId: Int, name: String) 

trait Error 

class UserStore { 
    def getUserDetails(userId: Int): Future[Either[Error, UserDetails]] = Promise.successful(Right(UserDetails(1, Some(1)))).future 
} 

class SchoolStore { 
    def getSchool(schoolId: Int): Future[Option[School]] = Promise.successful(Option(School(1, "Big School"))).future 
} 

object Demo { 
    import concurrent.ExecutionContext.Implicits.global 

    val userStore = new UserStore 
    val schoolStore = new SchoolStore 

    val user = User(1) 

    val schoolFuture: Future[Option[School]] = for { 
    ud <- userStore.getUserDetails(user.userId) 
    sid = ud.right.toOption.flatMap(_.schoolId) 
    s <- sid.map(schoolStore.getSchool(_)) 
    } yield s 
} 

Trả lời

13

This answer cho một câu hỏi tương tự về Promise[Option[A]] có thể hữu ích. Chỉ cần thay thế Future cho Promise.

Tôi suy luận các loại sau đây cho getUserDetailsgetSchool từ câu hỏi của bạn:

getUserDetails: UserID => Future[Either[??, UserDetails]] 
getSchool: SchoolID => Future[Option[School]] 

Vì bạn bỏ qua giá trị thất bại từ Either, chuyển nó đến một Option thay vào đó, bạn có hiệu quả có hai giá trị của loại A => Future[Option[B]].

Một khi bạn đã có một trường hợp Monad cho Future (có thể có một trong scalaz, hoặc bạn có thể viết riêng của bạn như trong câu trả lời tôi liên kết), áp dụng các biến OptionT cho vấn đề của bạn sẽ trông như thế này:

for { 
    ud <- optionT(getUserDetails(user.userID) map (_.right.toOption)) 
    sid <- optionT(Future.successful(ud.schoolID)) 
    s <- optionT(getSchool(sid)) 
} yield s 

Lưu ý rằng, để giữ cho các loại tương thích, ud.schoolID được gói trong tương lai (đã hoàn tất).

Kết quả của việc hiểu này sẽ có loại OptionT[Future, SchoolID]. Bạn có thể trích xuất một giá trị loại Future[Option[SchoolID]] bằng phương pháp run của trình biến đổi.

+2

Phải thừa nhận rằng scalaz làm tôi sợ một chút, tôi vẫn có một cách để đi trên đường cong học tập. Điều này giải quyết vấn đề cũng như tôi nghĩ rằng nó có thể được. Cảm ơn! – EvilRyry

+0

Cảm ơn bạn đã trả lời, nhưng liên kết đến thư viện scalaz-contrib bị hỏng –

+0

Bạn không cần 'scalaz-contrib nữa', vì các phiên bản tương lai của Monad hiện được cung cấp bởi scalaz bằng cách trộn đặc điểm' FutureInstances'. –

21

Mấu chốt ở đây là FutureOptionkhông soạn bên trong (Edited để đưa ra một câu trả lời đúng!) for vì không có chữ ký flatMap chính xác. Xin nhắc lại, cho desugars như vậy:

for (x0 <- c0; w1 = d1; x1 <- c1 if p1; ... ; xN <- cN) yield f 
c0.flatMap{ x0 => 
    val w1 = d1 
    c1.filter(x1 => p1).flatMap{ x1 => 
    ... cN.map(xN => f) ... 
    } 
} 

(nơi tuyên bố bất kỳ if ném một filter vào chuỗi - Tôi đã được chỉ là một ví dụ - và bằng tuyên bố chỉ cần đặt biến trước khi phần tiếp theo của chuôi). Vì bạn chỉ có thể flatMap khác Future s, mỗi câu tuyên bố c0, c1, ... ngoại trừ trường hợp cuối cùng có sản phẩm tốt hơn là Future.

Bây giờ, getUserDetailsgetSchool cả sản xuất Futures, nhưng sid là một Option, vì vậy chúng tôi không thể đặt nó ở phía bên tay phải của một <-. Thật không may, không có cách dọn dẹp nào để làm điều này. Nếu o là một lựa chọn, chúng ta có thể

o.map(Future.successful).getOrElse(Future.failed(new Exception)) 

để biến một Option thành một đã hoàn thành Future. Vì vậy,

for { 
    ud <- userStore.getUserDetails(user.userId) // RHS is a Future[Either[...]] 
    sid = ud.right.toOption.flatMap(_.schoolId) // RHS is an Option[Int] 
    fid <- sid.map(Future.successful).getOrElse(Future.failed(new Exception)) // RHS is Future[Int] 
    s <- schoolStore.getSchool(fid) 
} yield s 

sẽ thực hiện thủ thuật. Điều đó tốt hơn những gì bạn có? Nghi ngờ. Nhưng nếu bạn

implicit class OptionIsFuture[A](val option: Option[A]) extends AnyVal { 
    def future = option.map(Future.successful).getOrElse(Future.failed(new Exception)) 
} 

sau đó đột nhiên cho-hiểu trông hợp lý nữa:

for { 
    ud <- userStore.getUserDetails(user.userId) 
    sid <- ud.right.toOption.flatMap(_.schoolId).future 
    s <- schoolStore.getSchool(sid) 
} yield s 

Đây có phải là cách tốt nhất để viết mã này? Chắc là không; nó dựa trên việc chuyển đổi một None thành một ngoại lệ đơn giản chỉ vì bạn không biết phải làm gì khác tại thời điểm đó.Đây là khó khăn để làm việc xung quanh vì các quyết định thiết kế của Future; Tôi muốn đề nghị rằng mã ban đầu của bạn (mà gọi một bộ lọc) là ít nhất là tốt của một cách để làm điều đó.

+1

Điều này mang lại cho tôi một lỗi biên dịch. tìm thấy: scala.concurrent.Future [Tùy chọn [com.authorpub.userservice.School]] yêu cầu: Lựa chọn s <- schoolStore.getSchool (sid) – EvilRyry

+0

bạn có thể giải thích những gì tất cả các loại của bạn là [?]? Đó là một chút khó khăn để nói cho rằng bạn đã không được đăng đầy đủ mã làm việc. Tương lai là gì và tùy chọn trong mã làm việc của bạn là gì? Bạn có thể nhận Scala 2.10 để in ra loại biểu thức nếu bạn làm như sau: 'import scala.reflect.runtime.universe._; def typeme [A: TypeTag] (a: A) = {println (ngầm định [TypeTag [A]]); a} 'và sau đó bọc biểu thức trong' typeme', ví dụ: 'sid = typeme (ud.right.toOption.flatMap (_. schoolID))'. –

+2

Có người bị bỏ phiếu mà không có bình luận. Điều này không hữu ích lắm. Có gì sai với câu trả lời như bây giờ? –

8

Bạn muốn xảy ra hành vi nào trong trường hợp Option[School]None? Bạn có muốn Tương lai thất bại không? Với loại ngoại lệ nào? Bạn có muốn nó không bao giờ hoàn thành? (Nghe như một ý tưởng tồi).

Dù sao, mệnh đề if trong desugars biểu thức cho cuộc gọi đến phương thức filter. Hợp đồng Future#filter là như sau:

If the current future contains a value which satisfies the predicate, the new future will also hold that value. Otherwise, the resulting future will fail with a NoSuchElementException.

Nhưng chờ:

scala> None.get 
java.util.NoSuchElementException: None.get 

Như bạn thấy, None.get trả về chính xác những điều tương tự.

Do đó, loại bỏ các if sid.isDefined nên làm việc, và điều này sẽ trả về một kết quả hợp lý:

val schoolFuture = for { 
    ud <- userStore.getUserDetails(user.userId) 
    sid = ud.right.toOption.flatMap(_.schoolId) 
    s <- schoolStore.getSchool(sid.get) 
    } yield s 

Hãy nhớ rằng kết quả của schoolFuture có thể trong trường hợp của scala.util.Failure[NoSuchElementException]. Nhưng bạn chưa mô tả hành vi khác mà bạn muốn.

+3

+1 cho giải pháp không cần Scalaz;) * vịt * – iwein

2

Chúng tôi đã đóng gói nhỏ trên Future [Option [T]] hoạt động giống như một đơn nguyên (không ai kiểm tra luật đơn lẻ, nhưng có bản đồ, flatMap, foreach, filter và vv) - MaybeLater. Nó hoạt động nhiều hơn một tùy chọn không đồng bộ.

Có rất nhiều mã có mùi ở đó, nhưng có thể nó sẽ hữu ích ít nhất làm ví dụ. BTW: có rất nhiều câu hỏi mở

1

Nó dễ dàng hơn để sử dụng https://github.com/qifun/stateless-future hoặc https://github.com/scala/async làm A-Normal-Form transform (here cho ex.).

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