본문 바로가기

프로그래밍 기술 노트/Functional Study

[Monad] 내멋대로 Monad 이해하기 - Map, Return, Apply

FP 에서는 주로 예외처리를 Option 혹은 Maybe 라는 이름을 가진 타입을 사용한다.

Null 처리 같은것도 Option으로 대체 되는데 예를 들자면 다음과 같다.

MyClass nc = new MyClass();
//....
if (nc != null)
{
    //...
}
else
{
    //...
}

C#의 경우 위와 같이 Null로 확인한다면

let nt = Option<MyType>.None
//...
if nt.IsSome then
    //...
else
	//...

F# 은 위와같이 Option을 통하여 None 인지 Some 인지 확인한다.

 

Null이 천만불짜리 실수다~,Option 타입이 왜좋다~ 같은건 둘째 치고

단순위 위와같이 매번 if문 으로 Some 인지 None 인지 구분하여 사용한다면 Option을 쓰는 의미가 전혀 없어진다.

static void Main(string[] args)
{
    var a = new A();
    a.b = new B();

    if (a != null)
    {
        if (a.b != null)
        {
            Console.WriteLine(a.b.c);
        }
    }
}

이 코드나

let main argv =
    let a = Option<A>.Some({b = Option.Some({c=1})})

    if a.IsSome then
        if a.Value.b.IsSome then
            printfn "%A"  a.Value.b.Value.c

이 코드나 별반 다르지 않다. 아니 오히려 더 복잡할지도..?

아무튼 이렇게 사용하는건 비효율적이므로

Option 을 사용하여 값을 다룰때 Option 의 함수를 이용한다.

 

Option의 세상

Option타입을 다룰때에는 당연히 Option 을 사용하는 함수를 이용해한다.

그러나 세상함수들이 다 Option 전용 함수로 만들어져있지 않기때문에.. 우리는 함수나 값들을 Option 형식에 맞게 변환해주어야한다.

위 그림과 같이 Int 대신  Option<Int> 를 사용하고

Int 를 받아 String 을 반환하는 함수 대신 Option<int> 를 받아 Option<String> 을 반환하는 함수를 사용해야한다.

다음장을 살펴보자

 

Map

예를 들어 Option이 Int 형식을 가진다면

let ex = Option.Some(10)
let mappedEx = Option.map (fun x->x+10) ex
printfn "%A" mappedEx.Value

위와같이 mappedEx 는 ex가 Some이면 Value에 10을 더하고, Some 이 아니라 None 이면 걍 암것도 안한다.

Option.map 이라는 함수 자체가 함수와  Option 값을 받아 if ~ else 를 처리하는 고차 함수이기 때문이다 (정확하는 패턴매칭으로)

따라서 Map 함수는 아래와 같이 표현 할 수 있다.

Map의 형태

 M은 A를 감싸는 타입이라고 생각하면 되고 파랑색으로 표현된 부분은 함수의 형태라고 보면된다.

우리는 "Option 타입" 과 "Int 값을 받아 10을 더한 int 값을 반환하는 함수" 를 사용했기 때문에 아래와 같다.

위 표시에서 사용된 Value 와 함수를 표시하면 아래와 같다.

 

위와 같이 Map 이 동작한다.

즉 지금 우리가만든 함수는 int 를 받아 int를 리턴하는 함수이며, 이를 Option[int] 를 받아 Option[int] 를 리턴하는 꼴로 사용했다.

 

Map 의 경우 (int->int 꼴의 함수) 와 Option[int] 를 순서대로 파라미터를 받는다.

Option[int] 를 파라미터로 넘겨주지 않고 (int->int 꼴 함수) 만 파라미터로 넘겨주게 되면, Currying 으로 인하여 Option[int]를 받는 새로운 함수가 만들어지게된다.

let curriedMap = Option.map (fun x->x+10)
let mappedEx = curriedMap  ex

map 예시와 동일하게 당연하게 동작하는 함수이다. 그리고 시그니처를 확인할수있듯이 curryedMap은 Option[int] 를 받아 Option[int]를 리턴한다.

즉 원래의 int 를 받아 10을 더한 int를 리턴하는 함수가 Option[int] 를 받아 Option[int] 를 리턴하는 함수로 변한것이다.

일반 함수가 Option세상의 함수로 승급 "Lift" 했다고 한다. .

위와 같이 Map 을 통하여 Option 안에 있는 값을 안전하게 다룰수있다.

Return

함수의 값을 return 한다 할때의 return 이 아니고..

위의 Map 이 일반 세상의 함수를 Option 세상으로 바꾼거라면 Return 은 일반세상의 값을 Option세상의 값으로 바꾼것이다.

별건아니고 걍

let returnOption x = Option.Some x

요거다.

 

Apply

FP 세상에서는 함수도 1급 객체이다.

따라서 함수자체를 Option 에 담을수있다.

let f = (fun x->x+10)
let optionF = returnOption f
let curriedMap = Option.map f

10을 더하는 함수를 f 로 뺏고,

optionF 와 아까 사용한 curryedMap 을 보자

여기서 curryedMap 은 앞서 말했듯이 "Option[int] 를 받아 Option[int] 를 리턴하는 함수" 이다.

반면 optionF 는 "int를 받아 int를 리턴하는 함수"가 Option 안에 들어가있는것이다.

어마무시무시한 차이 이다.

여튼, 이렇게 함수 자체가 Option에 들어가 있을수 있다.

이런함수는 Option[int] 값을 파라미터로 받을수 없기때문에 바로 사용할수 없는데

이를 Option[int] 값을 받는 형태로 바꾸어주는것이 apply 이다.

module Option =
    let apply fOpt xOpt = 
        match fOpt,xOpt with
        | Some f, Some x -> Some (f x)
        | _ -> None

apply 함수를 보면 Option 에 들어간 함수와 값 x 를 빼서 (f x) 로 적용한값을 다시 Option 꼴로 바꾸어준다.

위와 같은 형태임을 알수있다.

 

Map Return Apply

3가지의 함수를 잘 표현한것이 아래 그림들 이다.

 

위 함수들을 이용하여 각종 함수와 값 을 Option 세상으로 가져올수있다.

 

 

참고자료

fsharpforfunandprofit.com/posts/elevated-world/

 

Understanding map and apply | F# for fun and profit

Part of the "Map and Bind and Apply, Oh my!" series (more) In this series of posts, I’ll attempt to describe some of the core functions for dealing with generic data types (such as Option and List). This is a follow-up post to my talk on functional patte

fsharpforfunandprofit.com

 

728x90