2016-06-16 5 views
11

F #의 모나드 주위에 머리를 쓰려고하고 있는데, 모범을 구성하는 예제를 찾고 있습니다.F에있는 모나드 조합 #

haskell에서 Monad Transformers를 사용하는 것처럼 보이지만 F #에서는 자신 만의 계산식 작성기를 만들 것 같습니다.

나는 그걸 막을 수 있지만, 표준 모나드의 조합과 사용법에 대한 예가 있습니까?

저는 특히 Reader, Writer 및 Either를 결합하여 환경에 도입하고 조정 한 다음 Writer를 사용하여 변경 사항을 환경으로 반환하는 데 관심이 있습니다. 어느 것이 성공과 실패를 구별하는 데 사용됩니다.

지금은 값 + 로그 또는 오류를 생성하는 EitherWriter 계산 식의 예제를 얻는 것이 좋습니다.

+1

IIRC, http://fsprojects.github.io/FSharpx.Extras가 있습니다. 또한 여기에 예제를 제공합니다 : http://blog.ploeh.dk/2016/04/11/async-as-surrogate-io –

+1

다음은 [리더, 작성자 및 상태 모나드를 결합한 예제]입니다 (http : // tomasp .net/blog/2014/update-monads /)에서 F #을 사용합니다. 당신이 설명하는 것을 수행하는 대안 * 방법은 F #에서이 [대수 효과 및 핸들러 사용 예제] (http://www.fssnip.net/jl)를 통해 이루어질 수 있습니다. –

+1

정확히 찾고있는 것이 없지만이 작은 프로젝트는 결합 된 리더/상태 모나드를 사용합니다 : https://github.com/TheInnerLight/RemoteMonad/ (Haskell) 코드의 일부 F # 번역입니다. 이 글 : http://www.cs.rhul.ac.uk/home/ucac009/publications/remote-monad.pdf 또한, (F #의 모나드 입출력을 위해) 개발중인 IO 라이브러리는 더 복잡한 숫자를 구현합니다 모나드 루프와 같은 모나드 연산은 꽤 잘 문서화되어 있습니다 : https : // github.com/TheInnerLight/NovelIO – TheInnerLight

답변

4

EitherWriter를 만드는 방법을 보여 드리겠습니다. EitherWriter을 주문하는 방법에 따라 다음 중 하나를 만들 수있는 방법은 두 가지가 있지만 그 예를 보여 드리겠습니다. 원하는 워크 플로우와 가장 비슷합니다.

라이터를 단순화하여 string list에만 기록하도록하겠습니다. 더 완전한 작성자 구현은 memptymappend을 사용하여 적절한 유형을 추상화합니다.

유형 정의 :

type EitherWriter<'a,'b> = EWriter of string list * Choice<'a,'b> 

기본 기능 :

나는 독립 모듈에 다음을 정의하고 내가 직접 사용하거나 계산 식을 만들 수를 참조 할 수 있습니다 좋아
let runEitherWriter = function 
    |EWriter (st, v) -> st, v 

let return' x = EWriter ([], Choice1Of2 x) 

let bind x f = 
    let (st, v) = runEitherWriter x 
    match v with 
    |Choice1Of2 a -> 
     match runEitherWriter (f a) with 
     |st', Choice1Of2 a -> EWriter(st @ st', Choice1Of2 a) 
     |st', Choice2Of2 b -> EWriter(st @ st', Choice2Of2 b) 
    |Choice2Of2 b -> EWriter(st, Choice2Of2 b) 

.

type EitherWriterBuilder() = 
    member this.Return x = return' x 
    member this.ReturnFrom x = x 
    member this.Bind(x,f) = bind x f 
    member this.Zero() = return'() 

let eitherWriter = EitherWriterBuilder() 

이 중 실제인가 : 다시 말하지만, 나는 간단하게 그냥 가장 기본적인 사용 가능한 구현을 할거야?

F 재미와 이익을위한 F는 railway oriented programming에 대한 훌륭한 정보와 경쟁 방식에 비해 장점이 있습니다.

이러한 예제는 사용자 지정 Result<'TSuccess,'TFailure>을 기반으로하지만 물론 F #의 기본 제공 Choice<'a,'b> 형식을 사용하여 동일하게 적용 할 수 있습니다.

우리는이 철도 방향 형식으로 표현 된 코드를 만날 가능성이 있지만 EitherWriter으로 직접 사용할 수 있도록 미리 작성된 코드는 거의 없습니다. 따라서이 방법의 실용성은 단순 성공/실패 코드에서 위에 제시된 모나드와 호환 가능한 것으로 쉽게 변환되는 것에 달려 있습니다. 여기

성공의 예/기능 장애 :

let divide5By = function 
    |0.0 -> Choice2Of2 "Divide by zero" 
    |x -> Choice1Of2 (5.0/x) 

이 함수는 제공된 번호 5로 분할한다. 이 숫자가 0이 아니면 결과를 포함하는 성공을 반환하고 제공된 숫자가 0이면 0으로 나누려고했음을 알리는 실패를 반환합니다.

이제는 이와 같은 기능을 EitherWriter에서 사용할 수있는 것으로 변환하는 도우미 함수가 필요합니다. 그렇게 할 수있는 함수는 이것이다 :

그것은, 실패를 기록하는 방법을 설명하는 기능을 성공을 로그인하는 방법을 설명하는 기능과 Either 모나드에 대한 바인딩 기능을 소요하고는 EitherWriter에 대한 바인딩 기능을 반환
let eitherConv logSuccessF logFailF f = 
    fun v -> 
     match f v with 
     |Choice1Of2 a -> EWriter(["Success: " + logSuccessF a], Choice1Of2 a) 
     |Choice2Of2 b -> EWriter(["ERROR: " + logFailF b], Choice2Of2 b) 

모나드.

우리는이처럼 사용할 수 있습니다

let ew = eitherWriter { 
    let! x = eitherConv (sprintf "%f") (sprintf "%s") divide5By 6.0 
    let! y = eitherConv (sprintf "%f") (sprintf "%s") divide5By 3.0 
    let! z = eitherConv (sprintf "%f") (sprintf "%s") divide5By 0.0 
    return (x, y, z) 
} 

let (log, _) = runEitherWriter ew 

printfn "%A" log 

을 그런 다음 반환

["Success: 0.833333"; "Success: 1.666667"; "ERROR: Divide by zero"]

+0

mappend/mempty 또는 인터페이스를 정의하는 데 사용되는 예가 있습니까? –

+1

@ user1570690 여기에 추상 클래스 예제가 있습니다. https://github.com/fsprojects/FSharpx.Extras/blob/master/src/FSharpx.Extras/Monoid.fs#L12 – TheInnerLight

+0

반대로, 철도 방향 상태 + 로그 또는 실패 인 모나드의 종류? –

6

"결합 된"빌더를 작성하는 것은 F # 에서 어떻게 수행 할 것인가?하면 그렇게 할 수 있습니다. 그러나 이것은 일반적인 접근법이 아니며 실용적인 접근법이 아닙니다.

하스켈에서 유비쿼터스 모나드가 하스켈에 존재하기 때문에 모나드 변압기가 필요합니다. F #의 경우는 아닙니다. 여기서 계산 워크 플로는 유용한 도구이지만 보완적인 워크 플로입니다. 맨 먼저 - F #은 부작용을 금지하지 않으므로 모나드 사용의 큰 이유 중 하나가 여기에 있습니다.

전형적인 접근법은 모델링하려는 계산의 본질을 포착하는 워크 플로를 식별하는 것입니다 (귀하의 경우 모나드가 될 것 같습니다). 그리고 나머지는 다른 방법을 사용하십시오. 값으로 계산하거나 로깅을위한 부작용 (일명 "로깅 프레임 워크")을 사용하여 수정 된 "환경"을 생성합니다.

4

을 내가 일반적으로 @TheInnerLight 대답은 F#+을 사용하고 F #으로하지만, 여기에 호기심 독자에 대한 관용적으로 간주되지 것을 알고 :

#r @"FSharpPlus.1.0.0-CI00089\lib\net40\FSharpPlus.dll" 

open FSharpPlus 

let divide5By = function 
    |0.0 -> Choice2Of2 "Divide by zero" 
    |x -> Choice1Of2 (5.0/x) 

let eitherConv logSuccessF logFailF f v = 
    ErrorT (
     match f v with 
     | Choice1Of2 a -> Writer(Choice1Of2 a, ["Success: " + logSuccessF a]) 
     | Choice2Of2 b -> Writer(Choice2Of2 b, ["ERROR: " + logFailF b] )) 

let ew = monad { 
    let! x = eitherConv (sprintf "%f") (sprintf "%s") divide5By 6.0 
    let! y = eitherConv (sprintf "%f") (sprintf "%s") divide5By 3.0 
    let! z = eitherConv (sprintf "%f") (sprintf "%s") divide5By 0.0 
    return (x, y, z) 
} 

let (_, log) = ew |> ErrorT.run |> Writer.run 

물론이 제품은 oid.

이 방법은 변압기는 모나드와 함께 작동하고, 위의 코드에서 당신은 쉽게, OptionT로 전환 SomeNoneChoice2Of2Choice1Of2을 대체 할 수 있으며, 그것은 바로 작동합니다, 기본적으로 하스켈 방법입니다.

개인적으로이 방법을 먼저 사용하는 편이 훨씬 쉽습니다. 원하는 기능이 있으면 트랜스포머를 커스터마이징하거나 해결할만큼 충분히 좋은지 그대로 둡니다.

+1

확실히 그것은 가능한 해결책을 얻는 더 빠른 방법입니다! – TheInnerLight