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 함수는 아래와 같이 표현 할 수 있다.
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/
'프로그래밍 기술 노트 > Functional Study' 카테고리의 다른 글
[Monad/Free Monad] 내멋대로 Free 모나드 이해하기 (0) | 2020.11.11 |
---|---|
[Monad] 내멋대로 Monad 이해하기 - Bind (FlatMap), Monad (0) | 2020.06.07 |
[Functional Study] Poker Game (0) | 2020.02.05 |
[Functional Study] Poker Game (0) | 2020.02.03 |
Clojure 에서 모나드는 필요없다? (0) | 2020.02.01 |