- 이말이 사실인게냐
discriminate는 (1)여러가지가 있을때 그것들이 서로 다르다는 걸 인식할 수 있다는 의미 (2) 특정한 단체의 사람들에게 against해서 쓰일때는 그 사람들에게 뭔가 부당하게 불이익이나 이익을 주는 의미를 가지고 있습니다.
즉, 둘다 차이에 근거한 의미인데요 첫째는 구별정도가 되겠고 둘째는 차별정도가 되겠네요. F#은 Discriminated Union(이하 union)이라는 아주 쓸모있는 기능을 가지고 있습니다. 그렇다면 F#은 차별을 권장하는 언어로서 인류발전에 그닥 긍정적이지 못한 언어일까요? 다행히 F#에서의 Discriminated는 첫번째인 구별의 의미로 쓰입니다.(뭐가 다행이지?-_-)
- 영단어엔 관심없는거임. 소스내놓으라해.
union의 형태는 아래와 같습니다.
type type-name = | case-identifier1 [of type1 [ * type2 ...] | case-identifier2 [of type3 [ * type4 ...] |
type키워드에 타입이름이 따라오고요 그리고 등호기호, 그리고 수직파이프(|) 문자로 나눠지는 식별자와 식별자의 타입리스트가 이어집니다. 저기서 각각의 식별자(case-identifier)를 discriminator(이하 구분자)라고 하는군요. 바로
type Expr = | Num of float | Add of Expr * Expr | Sub of Expr * Expr | Mul of Expr * Expr | Div of Expr * Expr |
위 코드는 간단한 사칙연산에 필요한 구분자를 union을 이용해서 선언한 코드입니다. 다섯개의 구분자를 정의하고 있는데요. Num은 그냥 float타입이고, 나머지는 float * float인 tuple타입입니다. 나머지코드를 보시기전에 이 union만 가지고 이야기 해보도록 하죠. 이걸 어케 쓸까요?
위의 그림을 보시면, 저렇게 쓰는거구나~ 하고 아실 수 있습니다. 구분자의 이름을 주고 괄호안에 구분자의 타입에 해당하는 값을 넣어주면 되는거죠. 그러면 Expr타입의 Num이라는 구분자이고 값은 float타입인 5.0을 가지고 있구나~ 하고 알마먹는 것이죠. Add도 마찬가지로 이해할 수 있습니다. 그리고 위의 union의 정의에 이어서 계산에 쓸 수식을 하나 정의해보죠.
let expr = Mul(Add(Num(5.0), Num(6.0)), Div(Num(5.0), Num(2.0))) // (5+6) * (5 /2) |
주석에 나와있듯이 (5+6) * (5/2)를 위의 구분자로 표현한 수식입니다. 그러면, union을 이렇게 선언해서 구분자를 저렇에 선언한다는건 알겠는데, 저걸 어따써먹는건지 하는 궁금증이 생깁니다. union과 찰떡궁합인게 바로 pattern matching입니다. 아래의 코드를 보시죠
let rec Calc expr = match expr with | Num num -> num | Add (e1, e2) -> (Calc e1) + (Calc e2) | Sub (e1, e2) -> (Calc e1) - (Calc e2) | Mul (e1, e2) -> (Calc e1) * (Calc e2) | Div (e1, e2) -> (Calc e1) / (Calc e2) |
위의 코드는 제귀호출이 가능한 Calc라는 메서드인데요 expr이라는 인자를 하나 받습니다. 그리고 그 expr을 가지고 일치하는 패턴을 찾습니다. 말로 하지 말고 그림으로 하겠습니다! 아래의 그림을 보시져.(잘 안보이심 클릭해서 크게보시길...-_-)
왼쪽 상단은 F# Interactive에서 입력한 부분이고, 오른쪽 하단은 패턴매칭하는 코드 부분이죠. Num(5.0)의 실제 타입은 "Num 5.0"이고 그게 그대로 "Num num"부분에 매칭이 되면서, num의 값은 5.0이 되는거죠. 패턴매칭의 결과로 (Num 5.0) 은 그냥 5.0을 리턴합니다. 그리고 Add(Num 4.0,Num 9.0)도 각각의 Num이 e1, e2에 매칭이 돼서 Calc를 다시 호출하는 모습입니다. 물론 결과적으로 (Num 4.0) -> 4.0, (Num 9.0) -> 9.0이 리턴되면서 두수의 합이 리턴되겠죠. 그러면 마지막으로 결과를 출력합니다.
Calc expr |> printfn "%f" |
위의 코드를 실행한 결과는 아래와 같습니다.
제대로 결과가 출력된 게 보이시죠? ㅋㅋㅋ. 그럼 오늘도 지난번 처럼 ildasm을 통해서 IL코드를 둘러볼까 합니다.
위 사진을 보시면 지난 포스트의 curry처럼 Expr도 클래스로 선언된걸 확인하실 수 있고, union의 구분자중의 하나인 Num도 Expr내부에 중첩된 클래스로 선언되어 있는걸 보실 수 있습니다. 그리고 Num이 float을 하나 가지는데 그게 num1이라는 이름으로 선언되어있는 걸 IL코드를 통해서도 확인해보실 수 있습니다. 즉, 내부적으로는 구분자별로 클래스를 선언해서 타입으로 검사를 하는걸로 생각할 수 있겠네요. 아래에 보시면 그 타입검사를 위한 메서드들이 선언되어있는걸 확인하실 수 있습니다.
그리고 마지막으로 MSDN에 나와있는 예제를 하나 보도록 하겠습니다.
type MilkOption = | Nonfat | TwoPercent | Whole | Soy | Rice type FlavorOption = string type Brand = string type Size = | Short | Tall type TeaFlavor = string type Shots = int type CoffeeType = | Drip | EspressoShot | Cappuccino | Latte of Size * MilkOption * Shots | FlavoredLatte of Size * MilkOption * FlavorOption * Shots type Drink = | Can of Brand | Coffee of CoffeeType | Tea of TeaFlavor let drink1 = Can("Coke") let drink2 = Coffee(Drip) let drink3 = Coffee(Latte(Tall, Nonfat, 2)) // A single short soy latte with hazelnut let drink4 = Coffee(FlavoredLatte(Short, Soy, "Hazelnut", 1)) |
union을 통해서 커피가 캔인지 뽑은 커피인지, 커피이외의 차종류인지부터 시작해서 커피의 종류에서, 사이즈, 샷의횟수등 까지 절묘하게 조합하는 모습을 보실 수 있습니다.
- 정리하며
F#은 이렇게 union을 쉽게쓸 수 있는 언어적 특성덕분에 DSL(Domain Specific Language, 다른말로 Language Oriented Progamming이라고도 함)을 잘 지원할 수 있는 능력이 있습니다. 물론 DSL은 그리 쉬운 주제는 아니지만, F#과 함께 천천히 시작해보는 것도 나쁘진 않을 거 같습니다. DSL이나 LOP에 대해서 더 설명을 드릴 수 있으면 좋겠지만, 내공이 허락치 않는군요-_-;;;;;;;;;;
- 참고자료
1. http://msdn.microsoft.com/en-us/library/dd233226(VS.100).aspx
2. http://sdasrath.blogspot.com/2009/02/20090220-f-types-discriminated-unions.html
3. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, Apress
'F#' 카테고리의 다른 글
Welcome to F#(12) - 공동작업 좋치아니항가 (3) | 2009.07.16 |
---|---|
Welcome to F#(10) - 인도음식 카레.....? (0) | 2009.07.03 |
Welcome to F#(9) - 메이져 데뷰. (2) | 2009.06.12 |
Welcome to F#(8) - 은총알과 엄친아. (2) | 2009.05.10 |
Welcome to F#(7) - 클리프 행어. (8) | 2009.04.26 |