모나드 다 제끼고, 아래와 같은 함수가 있다고 하자
type User = {id:int}
let nonSafeParse (str:string) =
match Int32.TryParse str with
| true, value -> value
| _ -> raise (ArgumentException("Invalid input"))
let nonSafeCreateUser (id:int) =
if id = 0 then raise (ArgumentException("Invalid input")) else {id=id}
let nonSafeParseAndCreateUser = nonSafeParse >> nonSafeCreateUser
let nonSafeWay1 = "1" |> nonSafeParse |> nonSafeCreateUser
let nonSafeWay2 = "1" |> (nonSafeParse >> nonSafeCreateUser)
let nonSafeWay2' = "1" |> nonSafeParseAndCreateUser
고차함수를 이용해서 함수를 합성하게 되면,
|> 과 같이 값을 계속 넘겨주는 "Pipeline" 방식과
>> 과 같이 함수를 합성하는 "Compose" 방식을 사용할 수 있다.
보통, Chaining 할떄는 Pilelining 하고, 별도의 함수를 만들때는 Compose 해서 만드는 경향이 있는데, 뭐 대충 상황에 맞게 쓰면 된다.
그런데, 만약 해당 함수가 모나드로 디자인 되어있다면?
[Monad] 내멋대로 Monad 이해하기 - Bind (FlatMap), Monad (tistory.com)
Bind (FlatMap) 라는것은 Monad 식 Chaining 일 뿐이다.
let tryParse (str:string) =
match Int32.TryParse str with
| true, value -> Some value
| _ -> None
let createUser (id:int) =
if id = 0 then None else Some {id=id}
let way1 = "1" |> tryParse |> Option.bind createUser
이렇게 Bind 를 이용해서 모나드의 Chaining 을 할수 있도록 한다.
그런데 Compose 는..?
let parseAndCreateUser = tryParse >> Option.bind createUser
위 처럼 만들수 있다. 그런데 Bind랑 합성하고 넘기면 되는데.. 잘 생각해보면 위 함수는 함수끼리 Compose 한게 아니라, 그냥 Manunally 하게 Binding 한것 같은 느낌이 든다.
마치 (모나드가 아닌) 일반 함수에서 아래처럼 쓴것 같은 느낌이다.
// let nonSafeParseAndCreateUser = nonSafeParse >> nonSafeCreateUse 이게 아니라
let nonSafeParseAndCreateUser x = (nonSafeParse x) |> nonSafeCreateUser
즉 일반함수에도 Compose 가 있듯이 Monad 의 Compose 가 따로 있었으면 좋겠다.
대충 그게 `Kleisli composition` 이다.
일반적으로 Operator 로 만들면 >=> 기호를 쓰는데, 물고기 처럼 생겨서 (??) fish Operator 라고 부른다
// fish operator (Kleisli composition)
let inline (>=>) a b x =
match a x with
| Some y -> b y
| None -> None
//let parseAndCreateUser = tryParse >> Option.bind createUser
let parseAndCreateUser = tryParse >=> createUser
let way1 = "1" |> tryParse |> Option.bind createUser
let way2 = "1" |> (tryParse >=> createUser)
let way2' = "1" |> parseAndCreateUser
즉 A -> B 꼴 함수가 아니라
`A -> F[B]` 꼴 함수가 합성되는데
A -> F[B] 이 시그니쳐 자체를 Type 으로 만들면 Kleisli 타입이다.
(대충 bind 쓸수있으면 Monad 이듯이, 대충 Kleisli Compose 를 쓸수있으면 Kleisli 타입이다)
'프로그래밍 기술 노트 > Functional Study' 카테고리의 다른 글
부분함수 (Currying) 에서 Generic 유지하기 in Kotlin / F# (0) | 2024.02.08 |
---|---|
[Algebraic Effect] 내 멋대로 대수적 효과 이해하기 feat Continuation (3) | 2022.12.15 |
[함수형 미세 팁] 파라미터 3개 이상의 Function 으 로 Reduce 하기 (1) | 2022.12.09 |
[liftIO] 빠르게 올리는 함수형 컨퍼런스 liftIO 2022 후기 (0) | 2022.12.03 |
Delimited Continuations 가 대체 뭔데? (1) | 2022.10.14 |