2017-04-14 3 views
3

저는 프로젝트에서 무료 모나드를 사용하기 시작했습니다. 저는 그것을 우아하게 만들기 위해 고심하고 있습니다.
두 문맥 (실제로는 더 있습니다) - ReceiptUser - 둘 다 데이터베이스에 대한 작업이 있으며, 나는 그들의 통역사를 별도로 유지하고 마지막 순간에 작성하고 싶습니다.
각기 다른 작업을 정의하고 Coproduct을 사용하여 하나의 유형으로 결합해야합니다.
나는 인터넷 검색 및 읽기의 일 후에 무슨이이다 : 나는이 작업을 수행 할 수있는 프로그램을 작성하려는 경우스칼라 무료 모나드 (Copadduct)와 모나드 변환기 (monad transformer)

// Receipts 
sealed trait ReceiptOp[A] 
case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]] 

class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) { 
    def getReceipt(id: String): Free[F, Either[Error, ReceiptEntity]] = Free.inject[ReceiptOp, F](GetReceipt(id)) 
} 

object ReceiptOps { 
    implicit def receiptOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F] 
} 

// Users 
sealed trait UserOp[A] 
case class GetUser(id: String) extends UserOp[Either[Error, User]] 

class UserOps[F[_]](implicit I: Inject[UserOp, F]) { 
    def getUser(id: String): Free[F, Either[Error, User]] = Free.inject[UserOp, F](GetUser(id)) 
} 

object UserOps { 
    implicit def userOps[F[_]](implicit I: Inject[UserOp, F]): UserOps[F] = new UserOps[F] 
} 

:

type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A] 
type Program[A] = Free[ReceiptsApp, A] 

def program(implicit RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[String] = { 

    import RO._, UO._ 

    for { 
    // would like to have 'User' type here 
    user <- getUser("user_id") 
    receipt <- getReceipt("test " + user.isLeft) // user type is `Either[Error, User]` 
    } yield "some result" 
} 

문제는 여기에 그 예를 user에 대한에 이해는 유형 Either[Error, User]이고 getUser 서명을 보면 이해할 수 있습니다.

내가 갖고 싶은 것은 User이거나 계산이 중지되었습니다.
나는 어떻게 든 EitherT 모나드 변압기 또는 FreeT을 사용해야한다고 알고있다. 그러나 시간을두고 노력한 후에는 작동시키기 위해 유형을 결합하는 방법을 모르고있다.

누군가 도움을 줄 수 있습니까? 자세한 내용이 필요하면 알려주십시오. 도움을 기꺼이 누군가가 그것을 실행할 수 있도록

내가 또한, 여기에 최소한의 SBT 프로젝트를 만들었습니다 https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonads.scala

건배, Leonti

Freek library이 문제를 해결하기 위해 필요한 모든 기계를 구현
+0

, 단지의 경우 클래스 인 getUser가'로'GetUser'을 정의 User]'를 호출하고 인터프리터가 오류를 처리하도록합니다. 'GetReceipt'와 유사합니다. –

+0

@ TomasMikula하지만 프로그램 내부에서 오류를 처리하고 싶습니다. 자동으로 처리되기를 원합니다. 이 기사를 보시기 바랍니다 : https://medium.com/iterators/free-monads-in-web-stack-part-i-2955d44757b5 남자는 무료 모나드가있는 EitherT를 사용하므로, 오류 계산은 둘 중 하나를 풀지 않고 자동으로 중지됩니다. – Leonti

+0

그래, 통역사가 그걸 처리하기를 원해. 'Free' 프로그램을 작성할 때 오류를 처리하고 싶지는 않습니다. 그 기사에는'행동'이'어느 쪽이든'을, 그리고 해석자'행동 ~> 이드'가 있습니다. 대신에,'Action'은 성공적인 결과를 리턴 할뿐 인터프리터'Action ~> [Error,?]'를 가질 수 있습니다. 최소한 사용자 쪽은 아니지만 '어느 쪽이나'는 필요하지 않습니다. 또한 인터프리터가 오류 유형을 결정하도록 남겨 둡니다. –

답변

1

:

자신을 재발견하면서 무료 모나드 등은 복잡한 제품을 거치지 않고도 확장 할 수 없습니다. 우아한 솔루션을 찾고 계시다면 Tagless Final Interpreters을 살펴 보시기 바랍니다. 고양이 긴 후 전투

+0

프리크 (Freek)는 훌륭한 라이브러리처럼 보입니다. DSL을 결합하는 것은 효과적이지만, 나는 이것을 시도 할 때'OnionT' 작업을하는 데 여전히 어려움을 겪고있다 : 'type O = 어느 쪽의 [Error,?] : & : Bulb' 컴파일러 오류가 발생한다 'not found : type ? ' 다음은 지금까지 내가 가지고있는 것입니다 : https://github.com/Leonti/free-monad-experiment/blob/master/src/main/scala/example/FreeMonadsFreek.scala – Leonti

+0

추가해야했습니다. 'kind-projector' 플러그인이 작동합니다.'addCompilerPlugin ("org.spire-math"% "종류 프로젝터"% "0.9.3"Cross CrossVersion.binary)' 정확하게 필요한 작업을 수행 중입니다. :) 심지어 가능하다면 Monad Transformer로 솔루션을보고 싶습니다. 이 시점에서 Freek는 나에게 순수한 마법입니다 :) – Leonti

1

: 당신이 원하는대로

// Receipts 
sealed trait ReceiptOp[A] 
case class GetReceipt(id: String) extends ReceiptOp[Either[Error, ReceiptEntity]] 

class ReceiptOps[F[_]](implicit I: Inject[ReceiptOp, F]) { 
    private[this] def liftFE[A, B](f: ReceiptOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f))) 

    def getReceipt(id: String): EitherT[Free[F, ?], Error, ReceiptEntity] = liftFE(GetReceipt(id)) 
} 

object ReceiptOps { 
    implicit def receiptOps[F[_]](implicit I: Inject[ReceiptOp, F]): ReceiptOps[F] = new ReceiptOps[F] 
} 

// Users 
sealed trait UserOp[A] 
case class GetUser(id: String) extends UserOp[Either[Error, User]] 

class UserOps[F[_]](implicit I: Inject[UserOp, F]) { 
    private[this] def liftFE[A, B](f: UserOp[Either[A, B]]) = EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f))) 

    def getUser(id: String): EitherT[Free[F, ?], Error, User] = Free.inject[UserOp, F](GetUser(id)) 
} 

object UserOps { 
    implicit def userOps[F[_]](implicit I: Inject[UserOp, F]): UserOps[F] = new UserOps[F] 
} 

은 그럼 당신은 프로그램을 작성 :

type ReceiptsApp[A] = Coproduct[ReceiptOp, UserOp, A] 
type Program[A] = Free[ReceiptsApp, A] 

def program(implicit RO: ReceiptOps[ReceiptsApp], UO: UserOps[ReceiptsApp]): Program[Either[Error, String]] = { 

    import RO._, UO._ 

    (for { 
    // would like to have 'User' type here 
    user <- getUser("user_id") 
    receipt <- getReceipt("test " + user.isLeft) // user type is `User` now 
    } yield "some result").value // you have to get Free value from EitherT, or change return signature of program 
} 

약간의 설명. 부산물 (Coproduct) 변압기없이, 함수는 반환 : 우리는 그림으로 작업의 부산물 (Coproduct)를 추가하면

Free[F, A] 

는 반환 형식이된다 : 우리가 EitherT로 변환하려고 할 때까지 잘 작동

Free[F[_], A] 

을. 만약 Coproduct가 없다면, EitherT는 다음과 같이 보일 것입니다 :

EitherT[F, ERROR, A] 

여기서 F는 Free [F, A]입니다.우리는 부산물 (Coproduct)의 유형을 추출해야 여기에 분명히 잘못

EitherT[F[_], ERROR, A] 

: F는 부산물 (Coproduct)이고 주사를 사용하는 경우에, 직관에 연결됩니다.

EitherT[Free[F, ?], A, B](Free.liftF(I.inj(f))) 

경우 : 지금은 우리가 올릴 수하는 올바른 유형

EitherT[({type L[a] = Free[F, a]})#L, ERROR, A] 

:

EitherT[Free[F, ?], ERROR, A] 

또는 람다 식으로 : 어떤 종류의-프로젝터 플러그인에로 우리를 이끌 것 반환 유형을 간소화하여 다음과 같이 할 수 있습니다.

와 같은 함수에서 사용은 : (: 문자열 ID) UserOp을 [확장 당신은`Free` 프로그램에서 오류를 처리하지 않으려면

def getReceipt(id: String): ResultEitherT[F[_], ReceiptEntity] = liftFE(GetReceipt(id)) 
관련 문제