2017-02-10 1 views
14

私のすべてのAPIメソッドはFuture [Option [T]]を返します。 :ユーザー、場所、およびアドレスは、私がscalazことを、すべてのオプション[ユーザー]、オプション[場所]とオプションの[アドレス]私のAPIはすべてFuture [Option [T]]を返します。for-comprでそれらをうまく組み合わせる方法

val up = for { 
user <- userService.getById(userId) 
location <- locationService.getById(locationId) 
address <- addressService.getById(addressId) 
} yield UserProfile(user, location, address) 

覚えているので、

case class UserProfile(user: User, location: Location, addresses: Address) 

次のコードは、現在コンパイルされません。私はOptionTを持っていますが、私は実際にそれを以前に使ったことがなく、私の状況にそれを適用する方法を確かめていません。

ユーザー、場所、またはアドレスが実際にNoneを返す場合、OptionTを使用して3つのモデルに適用する必要がある場合はどうなりますか?

+0

これは明示的に 'OptionT'に関するものなので、' scalaz'タグ(そしておそらくは 'monad-transformers'や' scala-cats')を持つべきでしょうか? –

答えて

21

完全な実施例のためにいくつかの簡単な定義:

import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.Future 

type User = String 
type Location = String 
type Address = String 

case class UserProfile(user: User, location: Location, addresses: Address) 

def getUserById(id: Long): Future[Option[User]] = id match { 
    case 1 => Future.successful(Some("Foo McBar")) 
    case _ => Future.successful(None) 
} 

def getLocationById(id: Long): Future[Option[Location]] = id match { 
    case 1 => Future.successful(Some("The Moon")) 
    case _ => Future.successful(None) 
} 

def getAddressById(id: Long): Future[Option[Address]] = id match { 
    case 1 => Future.successful(Some("123 Moon St.")) 
    case _ => Future.successful(None) 
} 

そして、完全を期すために、ここでScalazフリー実装は次のようになります:

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = 
    for { 
    maybeUser  <- getUserById(uid) 
    maybeLocation <- getLocationById(lid) 
    maybeAddress <- getAddressById(aid) 
    } yield (
    for { 
     user  <- maybeUser 
     location <- maybeLocation 
     address <- maybeAddress 
    } yield UserProfile(user, location, address) 
) 

すなわち、 mapを入れ替える必要があるのと同じように、私たちはfor-comprehensionsを入れ子にしなければなりません。 Intの値はFuture[Option[Int]]の内部にある可能性があります。

ScalazまたはCatsのOptionTモナドトランスは、この入れ子なしでFuture[Option[A]]のようなタイプで作業できるように設計されています。

import scalaz.OptionT, scalaz.std.scalaFuture._ 

def getProfile(uid: Long, lid: Long, aid: Long): OptionT[Future, UserProfile] = 
    for { 
    user  <- OptionT(getUserById(uid)) 
    location <- OptionT(getLocationById(lid)) 
    address <- OptionT(getAddressById(aid)) 
    } yield UserProfile(user, location, address) 

それともFuture[Option[UserProfile]]を望んでいた場合、あなただけのrunを呼び出すことができます:その後、

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = (
    for { 
    user  <- OptionT(getUserById(uid)) 
    location <- OptionT(getLocationById(lid)) 
    address <- OptionT(getAddressById(aid)) 
    } yield UserProfile(user, location, address) 
).run 

そして:

scala> getProfile(1L, 1L, 1L).foreach(println) 
Some(UserProfile(Foo McBar,The Moon,123 Moon St.)) 

中間結果のいずれかがある場合たとえば、あなたはこれを書くことができますNone、全体がNone

scala> getProfile(1L, 1L, 0L).foreach(println) 
None 

scala> getProfile(0L, 0L, 0L).foreach(println) 
None 

もちろん、いずれかの要求が失敗した場合、最初のエラーですべてが失敗します。脚注として

、リクエストが互いに依存していない場合は、あなたがそれらapplicatively代わりのmonadically構成することができます。より正確に

import scalaz.Scalaz._ 

def getProfile(uid: Long, lid: Long, aid: Long): Future[Option[UserProfile]] = (
    OptionT(getUserById(uid)) |@| 
    OptionT(getLocationById(lid)) |@| 
    OptionT(getAddressById(aid)) 
)(UserProfile.apply _).run 

このモデルの計算をし、それが実行できるため、より効率的かもしれリクエストを並行して処理します。

+0

それは私がホームランの男と呼ぶものです、ありがとう! | @ | Future.sequenceと同じですか? – Blankman

+0

@Blankmanええ、大きな違いは、それが異なるタイプ(型とアリティの両方を維持する)で動作することです。 –

+1

FYI: 'Future.onSuccess'はScala 2.12 https://github.com/viktorklang/blog/blob/master/Futures-in-Scala-2.12-part-5から非難されています。md – n4to4

関連する問題