Welcome to F#(12) - 공동작업 좋치아니항가

F# 2009. 7. 16. 14:38 Posted by 알 수 없는 사용자

- F#의 첫 라이브러리 출연(Feat. C#)

오늘은 간단하게, F#에서 만든 코드를 C#에서 사용해보는 시간을 갖겠습니다. 자~ 간단하게 간단하게~.

우선, C#으로 콘솔프로그래밍 프로젝트를 하나 생성하고 같은 솔루션에 F# 라이브러리 프로젝트를 하나 추가합니다. 대략 아래와 같은 모양이 됩니다. 



그리고 Module1.fs에다가 아래의 코드를 작성합니다. 


 module FirstModule =
    type Type1 =
        { num: int }
        member self.reverse = { num = -self.num }
        member self.add(num2) = { num = self.num + num2 }
       
type Type2 =
    { num: int }
    member self.reverse = { num = -self.num }
    member self.add(num2) = { num = self.num + num2 }

let add5 num = num + 5



FirstModule라는 모듈을 선언하고 그 안에 Type1이라는 타입을 선언한 걸 보여주고 있습니다. 그리고 Type1의 멤버로는 int타입의 num이 있고 reverse, add라는 함수가 있습니다. 각각의 함수뒤의 {}안의 코드는 num에 새로운 값을 할당해서 Type1의 새로운 객체를 리턴하는 내용으로 보시면 이해가 되실 겁니다. 그리고 그 아래에는 모듈선언없이 그냥 Type2라는 타입을 선언했구요, 그 밑에는 함수 add5를 선언했습니다. 이런 코드들을 C#에서 참조한다면 어떻게 쓸 수 있을까요? 

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CoWorkWithFSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            Module1.FirstModule.Type1 type1 = new Module1.FirstModule.Type1(5);
            Module1.FirstModule.Type1 type1_2 = type1.reverse;

            Console.WriteLine(type1_2.num);

            Module1.Type2 type2 = new Module1.Type2(6);
            Module1.Type2 type2_2 = type2.add(9);

            Console.WriteLine(type2_2.num);

            Console.WriteLine(Module1.add5(12));
        }
    }
}



위 코드를 보시면, Module1이라는 파일이름이 가장 먼저 오는 걸 볼 수 있습니다. 파일이름자체가 모듈이 되는거죠. 베타1이라서 그런지는 모르겠지만, F#으로 만든 코드는 "using Module1;" 형태로 using을 할 수 없었습니다. 에러메세지를 보면 네임스페이스가 아니라서 안된다고 하니 모듈도 클래스로 취급이 되는거 같은데 개선이 될지 아니면, 이대로 남을지 모르겠네요. 코드를 보시면 알겠지만, 매번 모듈명을 반복해서 적어줘야 하니 좀 빡세긴 합니다만;;;; 

아무튼, 저렇게 F#에서 작성한 타입의 객체를 생성할 수 있구요, 멤버 메서드와 함수를 호출할 수도 있습니다. 다만, 함수의 결과로 새로운 객체가 생성된다는 점 기억하시구요. 실행결과는 아래와 같습니다. 



그러면, 지난주에 만들었던 Discriminated Union을 이용한 간단한 사칙연산 코드를 C#에서 사용해보도록 하겠습니다. 아래코드를 Module1.fs에 추가하구요 

type Expr =
    | Num of float
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mul of Expr * Expr
    | Div of Expr * Expr

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)



아래 코드를 C#코드에 추가해줍니다. 

 Module1.Expr expr = new Module1.Expr._Mul(
    Module1.Expr.Add(
        Module1.Expr.Num(5.0),
        Module1.Expr.Num(6.0)),
        Module1.Expr.Div(
            Module1.Expr.Num(5.0),
            Module1.Expr.Num(2.0)));

Console.WriteLine(Module1.Calc(expr));



위 코드를 보시면, 지난주에 계산을 위해서 만들었던 식을 그대로 C#에서 생성했습니다. 실행결과는 아래와 같습니다. 




-정리하며

아~ 간단한 포스트였군요~. 대략 F#에서 만든 코드를 이런식으로 C#등의 다른 언어에서 호출해서 사용할 수 있습니다. 어느덧 베타1이 나온지도 꽤 됀 느낌이네요. 많이들 익숙해지셨나요? VS2008에서 뭔가를 만들다가 C#4.0의 dynamic을 쓰면 딱이겠다고 생각하는 순간, 아쉬움이 느껴지더군요. 아직 F#은 그만큼 익숙하지 못해서 그런경우는 없었던거 같네요-_-;;; 언제쯤 F#의 Jedi가 될 수 있을까요? ㅋㅋㅋ.


-참고자료

1. Expert F#, Don Syme, Adam Granicz,Antonio Cisternino, Apress

Welcome to F#(11) - 차별을 권장하는 언어인거임?!?!

F# 2009. 7. 8. 22:51 Posted by 알 수 없는 사용자

- 이말이 사실인게냐

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

Welcome to F#(10) - 인도음식 카레.....?

F# 2009. 7. 3. 13:39 Posted by 알 수 없는 사용자

- 카레랑 F#이랑 뭔 상관이야.

우리나라말로 카레. 영어로 curry인거죠. F#에선(함수형 언어에선) 카레를 맛보실 수 있습니다. 음미하고 먹으려면 좀 복잡한 과정을 거쳐야 할지도 모르지만요. 그래서 기꺼이 드셔보시고자 하시는 분들. 어떻게 먹는지 천천히 이야기 해보시죠 캬캬캬캬캬...


- 모야 먹는거 아니자나+_+

Curry는 수학과 컴퓨터과학에서 사용되는 특정한 기술을 가리키는 말로서, 미국의 수리논리학자였던 Haskell Brooks Curry의 이름을 따서 지어졌다고 합니다. Haskell이라는 언어도 Haskell Brooks Curry의 이름을 따서 만들어졌죠. Curry는 함수형 프로그래밍의 기초를 쌓았던 사람중의 한명이라고 합니다. 아마도 그래서 함수형언어중에 그의 이름을 딴 언어와 기능이 있는것 같습니다.

Curry의 사후에 새로만들 언어의 이름을 Haskell로 하기로 정하고 그의 미망인에게 가서 언어의 이름을 Haskell로 정해도 좋겠냐고 하자 흔쾌히 허락하면서도 그가 한번도 Haskell이라는 이름을 좋아한 적이 없었다고 말했다는 군요. 본인이 좋아했든 안했든 Haskell과 Curry라는 이름은 프로그래밍언어의 역사속에서 영원히 기억되겠죠. 아... 행복한 사람이구만 ㅋㅋㅋ. 완전 부럽네. -_- 

어쨌든 currying은 "다수개의 인자를 받는 함수를 하나의 인자를 받는 함수로 만들고 나머지 인자를 받는 함수를 리턴하는 변환의 절차"라고 합니다. 무슨 말인지 이해가 가시나요? 음-_-;; 간단히 알아보도록 하죠. 


위의 그림을 보시면, 우선 add라는 인자 두개를 받아서 더한 결과를 리턴하는 함수를 선언한 걸 보실 수 있습니다. 즉, "add 3 5" 같은 형태로 사용하는거 겠죠. 하지만, currying을 하게되면, 다수개의 인자(a, b 두개)를 받는 함수(add)를 하나의 인자(a)를 받는 함수로 만들고 나머지 인자(b)를 받는 함수를 리턴하게 할 수 있습니다. 다음 그림이 바로 그 내용이죠. 


보시면 add5는 add 5의 결과, 즉 "add a b"에서 a에 5를 넣은 "add 5 b"를 가리키게 됩니다. 즉, 커리의 정의에 따르면, add5는 여러개의 인자를 받는 함수(add)를 하나의 인자를 받는 함수로 변환해서(add 5) 나머지 인자를 받는 함수를 리턴합니다.

위 사진에서 add5의 타입을 보시면, (int -> int), int하나를 받아서 int를 리턴하는 함수임을 보여주고 있습니다. 하지만 add5는 받는 인자가 없습니다. 즉, "add 5 b"에서 인자 하나를 제외한 나머지 b를 add5가 인자로 받아서 5 + b의 결과를 리턴하는 함수라는 말이죠. 


그리고 result에 "add5 10"의 결과를 대입합니다. 그 결과는 add함수의 나머지 하나의 파라미터에 10을 집어넣은 즉, 5 + 10의 결과값인 15가 들어가겠죠.

 

 

위 그림에서 그 결과를 확인하실 수 있습니다. 위의 단계들을 그림으로 설명해보면 아래와 같습니다.

 

 

그리고 실제로 생성되는 IL코드를 통해서도 한번 확인해보도록 하겠습니다.

 

 

빌드한 코드를 ildasm을 통해서 보고있는건데요. 신기한건, add5가 클래스의 형태로 되어있다는 겁니다. ".ctor"은 생성자를 의미하죠. 그리고 멤버변수로 a가 들어가 있습니다. "add5 = add 5"가 클래스의 형태로 만들어지고 5는 멤버변수로 저장되어 있다가 나중에 호출될때 사용되는거 같군요. 그러면 add5의 호출부분인 Invoke쪽을 보겠습니다.

 

 

빨간박스로 표시된 부분을 위에서 부터 차례대로 보시면 add5는 "b"라는 int32형 인자를 하나 받아서 호출됩니다. 그리고 멤버변수인 a를 로드하구요, 그 둘을 가지고 add메서드를 호출합니다. 그렇습니다. 내부적으로는 이렇게 처리를 하는군요.

제가 왜 이렇게 curry에 대해서 설명을 드리는고 하니, 바로 F#의 기반에 그 curry가 깔려있기 때문이죠. 아래의 그림에서 뭔가 이상한걸 느끼셨다면, 바로 그게 curry입니다.

 

 

즉, "->'이게 들어가있으면 함수라는 이야기 인데, "add a = a + 5"같은 함수라면, 아... 이게 정수를 하나받아서 거기에 5를 더한 정수를 하나 리턴하는 구나.... 해서 "int -> int"라는게 이해가 되는데, 어째서 "add a b = a + b"는 "int -> int -> int"일까요? 그렇습니다. 내부적으로 curry를 사용하고 있기 때문입니다.

즉, "add 3 5"라고 해주더라도 curry를 통해서 "add 3 "을 통해서 3을 인자로 받고 정수형 인자 하나를 받는 함수를 리턴합니다. 그리고 그 함수에 5를 넘겨줘서 결과값인 8을 얻게 됩니다. 그래서 int를 받아서 "int -> int"인 함수를 리턴하고, 그 "int -> int"인 함수에 다시 int를 하나 넘겨줘서 결과로 int를 받는거죠. 이번에도 그림으로 정리해보면 아래와 같습니다.(별 도움이 안되는거 같지만 정성을 생각해서 자비좀....-_- ㅋㅋㅋ)

 

 

- 정리하며

넵, 오늘은 curry에 대해서 알아봤습니다. 충분히 설명을 못드린거 같아서 좀 불편한 마음이 있군요. 모든건 제 책임이니 저에게 따스한 격려를(?)....... 암튼, 이렇게 curry를 이용하게 되면, 위에서 보셨듯이 하나의 함수에서 새로운 함수들을 계속 해서 만들어 낼 수 있고, 그렇게 함수를 프로그램을 만들어가는 building block으로 사용할 수 있습니다. 더 구체적인 예를 드리지 못해서 아쉽군요. 더 공부해서 내공이 허락한다면 더 설명드리도록 하겠습니다.

 

- 참고자료

1. http://geekswithblogs.net/Podwysocki/archive/2008/02/21/119880.aspx
2. http://diditwith.net/2007/08/15/TheArtOfCurrying.aspx
3. http://www.dotnetrocks.com/default.aspx?showNum=310

Welcome to F#(9) - 메이져 데뷰.

F# 2009. 6. 12. 16:25 Posted by 알 수 없는 사용자
-뒷북이냐

어느덧 비주얼스튜디오 2010 베타1이 나온지도 거의 한달이 다되어 가네요. 그동안 뭐가 바뀌었는지 간략한 소개도 못드렸었군요. 뒷북이라 느끼실 수도 있겠지만, 간략하게 비주얼스튜디오 2010 베타1에서 F#이 어떻게 무명시절의 설움을 딛고 일어서고 있는지 소개해드리려고 합니다.




-메이저데뷰

개인적으로 가장 중요하다고 생각하는 점은 F#이 무명시절의 설움을 딛고 메이져데뷰를 했다는 거죠. 이제 당당하게 비주얼 스튜디오 시작시에 나오는 스플래시 화면에도 이름을 올리고, 비주얼 스튜디오를 깔고 처음실행하면 나오는 기본개발환경설정 선택창에서도 Visual F#을 보실 수 있습니다.




캬. 그리고 새 프로젝트를 눌러서 들어가보면 Visual F#이 메인에 나오고요 C#과 VB.NET은 "Other Languages" 카테고리로 분류가 됩니다. 좋네요. 참고로 F# Interactive도 기본으로 뜹니다. 


그리고 비주얼 스튜디오 2010 베타1에 포함된 F#은 약간 버전업이 되었는데요(1.9.6.2 -> 1.9.6.16) 아래 그림을 비교해보시면, F#의 버전과 닷넷프레임워크 버전이 바뀌었음을 확인해볼 수 있습니다.

(2008)
(2010)


그리고 이번버전부터 PowerPack을 따로 받아서 깔도록 바뀌었습니다. 그래서 F# Interactive에서 #r 해서 PowerPack.dll을 로드하려고 해도 dll을 찾아오지를 못하는데요, 그래서 따로 다운을 받아서 복사를 해줘야 합니다.  여기에서 받을 수 있구요~ "C:\Program Files\Microsoft F#\v4.0" 이 경로에 복사해서 넣어주면, F# Interactive에서 기존처럼 #r "FSharp.PowerPack.dll";; 구문으로 추가할때도 자동으로 찾아서 추가해줄 수 있습니다. 그리고 기존처럼 비주얼스튜디오 2008에서도 베타1에 추가된 1.9.6.16버전을 사용할 수 있는데요, 여기에서 다운받으실 수 있습니다.


그동안 6개월동안 커뮤니티의 피드백을 받아서 F#언어와 컴파일러의 버그를 잡고 구조와 성능을 개선하는데 집중했다고 합니다. #light가 이제는 기본으로 설정되는 것과 .NET 4.0의 BingInteger와 연동이 잘되고, params타입을 사용하는 메서드를 호출하거나 만들수있게 되었고, 라이브러리의 이름이나 표현을 표준에 맞게 수정하면서 타입파라미터가 대문자로 바뀌는 등의 변화가 있었습니다.


(소문자였던 타입 파라미터가)

(대문자로 바뀌었음)



-이게 다야?

사실, 더 자세하게 하나하나 분석해드릴 수 있으면 좋으련만, 공부한지 얼마 안되는지라 뭐가 자세하게 바뀐건지 감은 잡혀도 확실하게 설명드리지 못하는 점이 조금은 죄송스럽군요;;;
여기에서 추가되거나 바뀐 내용들을 자세하게 보실 수 있습니다.

F#이 도대체 왜 비주얼스튜디오에 정식으로 추가된 걸까요? 기존에도 J#등과 같은 언어가 추가되었다가 소리소문없이 사라지기도 했었습니다만, F#도 그런걸까요? 사실 저는 함수형언어에 기존의 언어들이 갖지 못한 장점들이 있고 C#이나 VB.NET이 멀티패러다임을 지향하면서 함수형,동적 언어들의 장점을 취합하고 있지만 함수형언어 고유의 영역을 제대로 커버하는건 힘든게 아닌가 싶었습니다. 그래서 F#이 추가되는 걸 그런 점에서 봤었습니다만, 스터디를 이끌고 계시는 K님의 말씀을 듣던 중 새로운 점을 하나 발견하게 됐습니다.

패션쇼를 예를 들어주셨는데요, 제대로 옮기는지는 확실치 않지만 대략 '패션쇼에서 독특한 옷들 입고 나와서 모델들이 걸어다닌다고 해서 그 옷이 바로 백화점이나 옷가게 진열장에 걸리는건 아니다, 앞으로의 가능성과 방향을 제시하는 의미가 있는거다' 같은 말씀이였습니다. 그리고 함수형언어가 그런면이 있어서 그동안 함수형언어의 발전적인 개념들이 현업에서 쓰는 언어에 하나씩 추가되기도 하는거라고 말이죠(LINQ같이).

이 말을 듣고 나닌 Paul Graham이 왜 그토록 Lisp을 최고로 치면서 시간이 지나면 사라질 언어말고 언어계층의 뿌리에 해당하는, 그래서 시간이 지나도 가치가 변하지 않을 Lisp같은 언어에 집중하라고 말을 했는지 알것 같기도 했습니다.

F#이 메이져 데뷰를 하면서 그걸통해 닷넷프레임워크에서 돌아가고 서로간에 호환이 가능한 함수형언어를 확보하는 동시에, F#을 통해서 시험적인 요소들의 가능성도 보고 닷넷을 더 발전적인 형태로 가져가기 위한 시도가 아닌가 하는 생각이 들었습니다.

제대로 설명한건지는 확신이 안서지만 어쨌든, 기존의 함수형언어의 팬들이 F#을 통해 뭔가를 많이 시도하고 즐긴다면 닷넷이 더욱 풍성해지는 거겠죠~~~~.

-참고자료
1. http://blogs.msdn.com/dsyme/default.aspx

Welcome to F#(8) - 은총알과 엄친아.

F# 2009. 5. 10. 20:56 Posted by 알 수 없는 사용자
-넌 제목이 왜 맨날 이 모냥이냐.

은총알과 엄친아. 과연 전 무슨생각으로 제목을 맨날 이렇게 짓는 걸까요? 개인적으로 포스트에서 이야기 하고자 하는걸 비유적으로 설명하는걸 좋아합니다. 그래서 일까요? 늘 글을 쓰기 전에 이야기 하고자 하는 내용을 심사숙고합니다. 그렇게 해서 나오는게 고작 요정도 인거죠.

은총알. 은초딩도 아니고 이건 뭘까요? 들어보신분들도 많으시겠지만, 소프트웨어 공학에서 이야기하는 모든 문제가 이거하나면 해결된다고 하는 그런 어떤 경우에도 쓸 수 있는 만능의 해결책? 그정도 느낌을 생각하시면 될거 같습니다. 즉, 많은 사람들을 파닥파닥 낚이게 만드는 주범이지요. 엄친아도 못하는게 없는 만능의 엄마친구아들을 뜻하죠. 적어도 프로그래밍 세계에선 은총알이나 엄친아 같은 프로그래밍언어는 없는거 같습니다. 아무리 C#이 발전했다 한들, 하자면 할 수 있지만 어색한 부분이 있다는 거죠. 그럼 오늘은 거기에 대해서 좀 이야기 해보겠습니다.


- 이거슨 F# 코드!

아래 코드는 Expert F#에 나오는 코드입니다.

open System.Collections.Generic

let devideIntoEquivalenceClasses keyf seq =
    let dict = new Dictionary<'key, ResizeArray<'a>>()
    
    seq |> Seq.iter(fun v ->
        let key = keyf v
        let ok, prev = dict.TryGetValue(key)
        if ok then prev.Add(v)
        else 
            let prev = new ResizeArray<'a>()
            dict.[key] <- prev
            prev.Add(v))
            
    dict |> Seq.map(fun group -> group.Key, Seq.readonly group.Value)

즉, 어떤 데이터의 집합과 그 데이터에 대해 수행할 함수를 받아서 각각의 데이터에 대해 그 함수를 수행합니다. 그리고 같은 결과 끼리 집합을 만들어서, 결과값과 결과값이 같은 데이터들의 집합을 Dictionary로 만들어서 리턴해주는 프로그램이죠. 즉, 함수형언어의 장점이 잘 드러나는 데이터처리부분의 예제입니다. 실행결과는 아래와 같습니다. 

> devideIntoEquivalenceClasses (fun x -> x % 3) [1..10];;
val it : seq<int * seq<int>>
= seq [(1, seq [1; 4; 7; 10]); (2, seq [2; 5; 8]); (0, seq [3; 6; 9])]

숫자를 3으로 나눈나머지를 구하는 함수와, 1부터 10까지의 숫자를 넘겨주면, 이렇게 나머지값이 같은 숫자별로 그룹을 지어서 리턴해줍니다. 여기서 Dictionary는 닷넷의 클래스를 그대로 사용한 거구요, ResizeArray는 닷넷에서의 System.Collections.Generic.List<T>의 줄임말입입니다. 이름그대로 mutable한, 사이즈를 조절가능한 Array라는 거죠. 

코드를 보면, 전체데이터를 포함할 dict라는 Dictionary를 선언하구요, 입력으로 들어온 seq의 각요소에 대해서 사용자가 넘겨준 함수(keyf)를 수행합니다. 그리고 그 결과값이 이미 존재하는지 확인해서 존재한다면, 그 결과값을 key로 하는 ResizeArray에 숫자값을 추가하고 존재하지 않는다면, 새로운 ResizeArray를 만들어서 숫자를 추가하고, 그 ResizeArray를 dict에 추가해주는 방식입니다.

그리곤 마지막으로 key에 해당하는 ResizeArray를 읽기전용인 시퀀스로 만들어서 리턴해주고 있습니다. 이 예제는 어떤 함수에서 내부적으로는 side-effect를 가진 자료구조를 쓰더라도 그 side-effect를 오직 내부에서만 나타나게 격리한다면, 그 함수는 pure하고 functional한 함수라는 이야기를 하면서 나온 예제입니다. 그래서 마지막에 읽기전용 시퀀스로 리턴을 해주고 있는거지요. 


- 이거슨 C#코드!

이 코드를 C# 3.0버전으로 그대로 옮겨 보겠습니다. 

class Program
    {
        public Dictionary<T2, List<T1>> DivideIntoEquivalenceClasses<T1, T2>>(Func<T1,T2> func, List<T1> list)
        {
            Dictionary<T2, List<T1>> dict = new Dictionary<T2, List<T1>>();

            list.ForEach(l => {
                T2 key = func(l);
                if (dict.ContainsKey(key))
                {
                    dict[key].Add(l);
                }
                else
                {
                    List<T1> prev = new List<T1>();
                    dict[key] = prev;
                    prev.Add(l);
                }
            });

            return dict;
        }

        static void Main(string[] args)
        {
            Program program = new Program();

            var dict = program.DivideIntoEquivalenceClasses((x => x % 3), new List<INT> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });

            var resultSet = dict.Select(item => new { item.Key, item.Value });

            foreach (var result in resultSet)
            {                
                Console.Write("{0},", result.Key);
                result.Value.ForEach(item => Console.Write(" {0} ", item));
                Console.WriteLine();
            }
        }
    }

실행결과는 아래과 같구요.



역시 func<>를 이용한 higher-order와 LINQ덕분에 코드의 길이자체가 별로 차이가 없고, 오히려 C#의 문법에 익숙해서 그런지 더 깔끔해 보이는 부분도 있네요. 다만, 눈에 상당히 거슬리는게 있다면, T1,T2를 비롯한 파라미터타입과 리턴타입등을 명시한 부분이 상당히 거슬리는군요. 사실 짜면서도 아직 익숙하지 않아서 일까 좀 헷갈렸습니다. F#에서는 Type inference가 제대로 적용되어서 파라미터에도 타입을 명시할 필요가 없죠. 물론 C#에서도 변수수준에서는 var타입을 이용해서 Type inference를 지원하지만, 파라미터와 리턴타입에 대해서는 지원하지 않기 때문에 쫌 복잡해질 수 있습니다. 함수형 프로그래밍을 C#에서 하느라 복잡해지는 모냥은 여기에서도 확인하실 수 있습니다. @_@.

 
아래구절은 Expert F#에서 인용한 구절인데요, 제가 설명하려는 바를 아주 잘 요약해주시고 있습니다.

 A key feature of F# is the automatic generalization of code. The combination of automatic
generalization and type inference makes many programs simpler, more succinct, and more

general. It also greatly enhances code reuse. Languages without automatic generalization force programmers to compute and explicitly write down the most general type of their functions, and often this is so tedious that programmers do not take the time to abstract common patterns of data manipulation and control.

F#의 핵심기능중의 하나가 바로 코드를 자동으로 일반화시켜주는 것이다. 자동일반화와 타입유추를 조합하게 되면 많은 프로그램을 간단하고, 좀더 명확하게 그리고 좀 더 일반화시켜서 작성할 수 있다. 당연히 코드 재사용도 아주 편리해 진다. 자동일반화를 지원하지 않는 언어는 프로그래머가 명시적으로 일반적인 타입을 계산해서 명시해야 만 한다. 그리고 이런 것들은 데이터처리와 그 처리의 흐름을 공통적인 부분을 묶어서 추상화하는 작업을 매우 손이 많이가고 짜증나는 작업으로 만든다.



- 그래서~?

즉, LINQ를 통해 C#에서의 데이터처리와 흐름의 추상화가 가능해졌으며, 그 덕에 C#으로도 부분적으로 함수형언어와 버금가는 생산성을 내는일이 가능해졌습니다. 하지만, 명령형 언어의 한계점이 이런부분일까요? 일반화를 하기 위해서 무척이나 까다롭게 인자들의 타입을 잘 명시해줘야 하는거죠. 물론, 저렇게 하는게 옳은지에 대해서는 잘 모르겠습니다. 개인적으로는 저런부분을 굳이 C#을 이용해서 구현하려고 애쓰기 보다는 F#을 이용해서 조합하는게 더 옳지 않나 싶습니다. 사실 F#을 닷넷에 편입시키려는 의도도 그런부분을 가장 기대하는게 아닌가 싶기도 하구요. 


- 참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS
2. http://weblogs.asp.net/podwysocki/archive/2009/04/26/functional-c-forward-functional-composition.aspx

 

Welcome to F#(7) - 클리프 행어.

F# 2009. 4. 26. 18:22 Posted by 알 수 없는 사용자

-거인의 어깨에 위태롭게 매달려서 안간힘-_-.

여전히 함수형언어를, F#을 이해하기 위해서 몸부림 치고 있습니다. 제 짧은 생각인지 모르겠으나, F#을 처음배우는 프로그래밍언어로 생각하시는 분은 없을거라고 생각합니다. 호기심이든 다른 목적이 있어서든 기존에 주력으로 쓰는 명령형 언어가 있고 추가로 공부하려고 하시는 분이 거의 대부분이 아닐까 합니다.(게다가 F#은 C#이나 VB.NET을 하시던 분이 거의 대부분이 아닐까 싶네요) 저 역시 C#을 주력으로 쓰면서 F#을 공부하고 있는거니까요.

그래서 단순히 문법적 차이를 말씀드리기 보다는 함수형언어에 깔려있는 사상&접근법&관점의 차이에 대해서 말씀드리고자 하는데, 부족한 지식으로 글이 들쭉날쭉하고 앞내용과 뒷내용이 연결이 안되는, 한글이 생긴이래 진급을 못하고 있는 불상사도 자주 보게되는거 같습니다. 

오늘도 함수형언어를 이해하기 위한 노력의 일환으로 한 논문의 내용을 바탕으로 이야기 하도록 하겠습니다. Anders hejlsberg가 언어설계에 대한 이야기를 하면서 자신은 다른 거인의 어깨위에 서있다고 말을 했었는데, 저는 거인의 어깨에 선게 아니라 위태롭게 매달려 있다는 느낌이 드는군요.-_-;  아놔, 오늘도 서론이 길군요-_-;;

 

-무슨 논문이냐.

제가 한번 
"Why Functional Programming Matters?"라는 논문에 대해서 언급한적이 있었습니다. 오늘 포스트의 바탕이 될 이 논문에서 모듈화에 대한 이야기를 하면서 다음과 같은 이야기를 합니다. 

However, there is a very important point that is often missed. When writing a modular program to solve a problem, one first divides the problem into subproblems, then solves the sub-problems and combines the solutions. The ways in which one can divide up the original problem depend directly on the ways in which one can glue solutions together. Therefore, to increase ones ability to modularise a problem conceptually, one must provide new kinds of glue in the programming language. Complicated scope rules and provision for separate compilation only help with clerical details; they offer no new conceptual tools for decomposing problems. 

하지만, 그런 이야기에서 중요한 점이 무시되곤 한다. 어떤 문제를 해결하기 위해서 모듈화된 프로그램을 작성할때, 프로그래머는 우선 문제를 작은 문제로 분리해내야 하고, 각각의 작은문제를 해결해서 하나의 솔루션으로 조합해야 한다. 문제를 어떻게 분리할지는 전적으로 해결된 문제들을 어떻게 하나의 솔루션으로 조합할지에 달려있다. 그러므로 프로그래머가 조금더 능숙하게 문제를 모듈화해서 해결하게 해주려면, 프로그래밍언어에서 문제를 붙이기 위한 새로운 접착제를 제공해줘야 한다.  복잡한 스코프룰이나 분리컴파일을 좀 더 편리하게 할 수 있게 도와주는 것들은 그저 사무적인일(문제를 해결하는일 자체보다는 코드작성을 도와주는 부차적인일)정도를 도와줄 뿐이다. 즉, 문제를 잘개 쪼개기 위해선 아무 도움도 안되는 것이다.


그러면서 저자는 함수형언어에서 문제를 붙이기 위해 쓰이는 두가지 접착제를 소개합니다. 그 중 하나가 지난포스트에서도 언급했었던 higher-order function입니다. 기억나시나요? 함수를 인자로 받고 함수를 리턴가능한 함수가 higher-order function이었죠? 이 논문은 Miranda라는 언어를 사용하면서 Haskell을 쓰지 못해서 죄송하다고 언급하고 있습니다. 하지만, 조금 된 논문이라 F#으로 못해서 죄송하다는 말은 없군요. 크하하하-_-;;

그럼 저자가 higher-order function의 예로 언급한 reduce를 F#으로 구현해보면서 설명드리도록 하겠습니다. reduce는 MapReduce때문에 유명해지기도 했고, fold라는 이름으로도 불리는데요, 아마도 fold나 reduce나 둘다 뭔가를 하나씩 줄여나가는 이미지를 연상하면서 붙인 이름이 아닌가 싶습니다.


우선, 원문을 보실분들을 위해서 Miranda에 대해서 아주초큼 설명을 드리자면, Miranda는 ML에서 ML은 Lisp에서 출발한 언어라고 합니다. Lisp에서는 리스트를 Pair의 조합으로 표현하는데요, 여기서도 그런거 같습니다. Pair의 조합이란 cons(construct의 준말이라는 군요)라는 함수를 통해서 만들어지는데요(Lisp이나 Scheme하신분들은 많이 보셨을듯~) 인자를 두개 받아서 그 두 인자를 하나의 쌍으로 묶어서 리턴하는 함수입니다. 즉 Lisp에서 (1 2 3 4)라는 리스트는,

 

 (cons 1

(cons 2

(cons 3

(cons 4 nil)))) 


nil은 Lisp에서 빈값을 뜻합니다, (1 2)는 (cons 1 2), (1 2 3)은 (cons 1 (cons 2 3))의 결과 인거죠. F#이 OCaml에서, OCaml은 ML에서 출발했으니, F#에서도 리스트는 비슷하게 구현되지 않았을까 싶습니다.(List.hd, List.tl같은 함수를 보면 그런거 같습니다.) 그리고 두개의 값이 하나의 쌍으로 이루어 져있기 때문에, 값에서 head부분과 tail부분으로 나눠서 생각할 수 있습니다. (1 2 3)의 head는 1이고 tail은 (2 3)입니다. (4 5 6 7)의 head는 4이고 tail은 (5 6 7)인거죠

 

-그래 그래 얼른 reduce가 뭔지나 좀 보자.

일단, 숫자의 리스트를 모두더하는 sum을 생각해보도록 하죠. 빈값에 대한 sum은 0이겠죠? 그래서 아래와 같이 정의합니다. 

sum [] = 0 

그리고 리스트의 합은 head의 값과 나머지 tail의 합으로 정의할 수 있고, 그 tail의 합은 다시 head의 값과 나머지인 tail의 합으로 정의할 수 있습니다. 즉, 

sum list = List.hd(list) + sum List.tl(list) 

이렇게 정의할 수 있습니다. List.hd는 head의 약자로 List에서 head를 리턴하고, tl은 tail의 약자로 List에서 head를 제외한 나머지 부분을 리턴합니다. 이런 과정을 어떤 함수가 들어와도 가능하게 일반화 하려면, 위에서 빨간색(?)으로 음영표시한 두부분이 중요해 집니다. 즉, '리스트의 마지막에 도달했을때 어떤 값을 리턴할 것인가'와 '리스트의 head와 tail에 대해 어떤 함수를 수행할 것인가'하는 부분입니다. 예를 들면, 리스트에 대해서 곱셈을 수행하는데, 빈리스트에 대해서 0을 리턴하게 한다면 리스트의 곱셈값은 0이 되겠죠-_-;; 그래서 이럴땐 빈리스트의 값을 곱셈에 영향을 안주는 1로 설정해야 합니다. 물론, 위의 경우에서는 합에 영향을 주지 않는 0으로 선택한 것이구요. 

그럼, 각 리스트의 요소에 대해 수행할 함수를 아래와 같이 정의합니다. 

let add x y = x + y 

그리고 다음과 같이 reduce함수를 작성해줍니다. 

 let rec reduce f x list =
    if list = [] then x

    else f (List.hd list) ((reduce f x) (List.tl list)) 

즉, reduce는 자기자신의 정의속에 자신을 호출하는 부분이 있는 제귀함수이고, 인자로는 f, x, list를 받으며 list가 빈리스트라면 연산에 영향을 주지않는 값으로 x를 리턴하고, 빈 리스트가 아니라면 head와 나머지리스트에 함수를 f를 적용한 값을 가지고 f를 수행합니다. 위의 메서드를 좀 더 읽기 쉽게하기 위해서 타입을 명시해주면 아래과 같습니다.

let rec reduce (f : 'a -> 'b) (x : 'c) (list : 'd list) =
    if list = [] then x

    else f (List.hd list) ((reduce f x) (List.tl list))

즉, f는 함수이고, x는 제네릭한 값하나, list는 제네릭한 리스트인거죠. 일단, 수행결과를 보고 무슨 소리인지 더 풀어보도록 하죠. 위 두 함수의 정의를 평가해주고나서, reduce add 0 [1;2;3;4] 이렇게 실행을 하면 아래와 같은 결과가 나옵니다. 

val add : int -> int -> int

>

val reduce : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b 

> reduce add 0 [1;2;3;4];;
val it : int = 10 

리스트의 값을 모두 더해서 10이라는 값이 나왔군요. 이 값이 어떻게 나왔는지 reduce가 정의를 확장하고 줄여나가는 모습을 자세히 적어보면 아래와 같습니다.

 

즉,
add x y = x + y이고
(reduce add 0) [1;2;3;4] 라면..
= add 1 ((reduce add 0) [2;3;4])
= add 1 (add 2 ((reduce add 0) [3;4]))
= add 1 (add 2 (add 3 ((reduce add 0) [4])))
= add 1 (add 2 (add 3 (add 4 ((reduce add 0) []))))
= add 1 (add 2 (add 3 (add 4 0)))
= add 1 (add 2 (add 3 4))
= add 1 (add 2 7)
= add 1 9
= 10

음영으로 강조한 부분이 reduce가 확장되는걸 보기 쉽게 만든 부분입니다. 자신의 정의를 이용해서 빈리스트가 나올때까지 값을 확장하고, 하나씩 줄여가면서 값을 게산해 가는거죠. 사실 add는 이미 F#에 구현되어 있습니다. 아래와 같이 쓸 수도 있습니다. 

reduce (+) 0 [1;2;3;4] 

그리고 람다를 이용해서 아래와 같이 써도 똑같은 결과를 낼 수 있습니다. 

reduce (fun x y -> x + y) 0 [1;2;3;4] 

그리고 reduce를 이용해서 아래와 같은 연산들도 가능합니다. 

reduce (&&) true [true; true; true; false; true] //전부다 true인지 검사하는 함수
let append a b = reduce (fun x y -> List.Cons(x, y)) b a //두 리스트를 붙이는 함수 

그리고 아래는 append의 실행결과입니다.

> append [1;2] [3;4];;

val it : int list = [1; 2; 3; 4] 

List.Cons는 위에서 말씀드렸던 cons의 F#의 List버전입니다. 위 메서드를 확장하고 줄여가나는 모습을 자세히 그려보면 아래와 같습니다.

List.Cons(1, reduce (fun x y -> List.Cons(x, y)) [3;4] [2])

= List.Cons(1, (List.Cons (2, reduce (fun x y -> List.Cons(x, y)) [3;4] [])))

= List.Cons(1, (List.Cons (2, [3;4])))

= List.Cons(1, [2;3;4])

= [1;2;3;4]

 

즉, 리스트에 대해서 특정함수의 수행을 누적시켜서 결과를 얻는(Aggregate) 과정을 쪼개고 그 쪼갠 부분을 붙이는 방법으로 higher-order function을 사용해서 만든 reduce로 이렇게 다양한 연산이 가능한 것입니다. 물론 논문의 저자는 여기서 더 나아가고 있습니다만, 제 실력부족으로 더 이상 나가지 못하는 점 양해바랍니다.-_-;;; 아, 그리고 추가로 말씀드리자면 reduce는 F#에 이미 구현되어 있습니다. reduce가 fold라고 불리기도 한다고 위에서 말씀드렸었죠? 아래와 같이 하면 reduce add 0 [1;2;3;4]와 같은 결과를 낼 수 있습니다. 

List.fold_right (+) [1;2;3;4] 0

List.fold_left (+) 0 [1;2;3;4] 

둘의 차이점은 아래와 같습니다. 

(출처 : http://en.wikipedia.org/wiki/Fold_(higher-order_function))


아래그림은 fold_left이고, 윗그림이 fold_right입니다. 그리고
각각 그림의 왼쪽은 리스트이고, 오른쪽은 어떻게 펼치느냐에 대한 도식입니다. 즉, 왼쪽으로 펼쳐서 접느냐, 오른쪽으로 펼쳐서 접느냐에 따라 틀린거지요. 위에서 구현된 reduce는 fold_right라고 보시면 됩니다.

 

- 마치면서

사실, 진정한 함수형언어의 강점과 F#의 강점은 아직 맛도 못봤습니다. 이거 빨리 제가 맛을 보고 잘 전해드려야 할텐데;; 개인적인 사정이 점점 안좋아지는군요-_-. Anders hejlsberg의 인터뷰에서 발췌한 글을 끝으로 남기면서 글을 마치겠습니다.(왜 Anders hejlsberg이야기가 많이 나오냐면, 전  Anders hejlsberg빠돌이거든요-_-;) 이 글을 읽다보니, 왜 함수형언어의 힘을 빌린 LINQ를 추가해서 데이터를 다루는 부분을 추상화 했는지 확 다가오는 군요. 그리고 앤더스가 선언형 언어와 함수형언어의 장점을 점점 더 중요하게 생각하고 있다는 생각이 듭니다.(물론, 원문을 읽어보시면 더 자세한 내용을 보실수 있겠져~.) 그리고 언제나 그렇듯이 제 맘대로 번역-_-.

 

I think one of the things that are true of most programming languages today is that they force you to overspecify the solution to your problem. You're down there writing nested for loops and if statements and whatever, and really all you wanted to do was a join between two pieces of data. But there's nothing that allows you to say that. You have to get down and dirty and do hash tables and dictionaries, blah, blah, blah.

난 요즘 대부분의 프로그래밍언어가 프로그래머에게 문제를 해결하기 위한 솔루션을 너무 필요이상으로 서술하게 만든다고 생각합니다. 물론 사실이구요. 프로그래머는 그저 두 데이터집합을 조인하고 싶을 뿐인데, for루프랑 if문을 여러개 중첩시키고 어쩌고 저쩌고 해야만 하는거죠. 즉, 그냥 조인하게 해달라고만 말할 수 있는게 없다는 겁니다. 진흙탕으로 내려가서 해시테이블이랑 딕셔너리랑 기타등등, 뭐 그런것들이랑 열심히 뒹굴어야 하는거죠.



-참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS
2. Programming Language Pragmatics 2nd Edition, Michael L. Scott, Morgan Kaufmann Publishers
3. Structure and Interpretation of Computer Programs, 2nd Edition, Harold Abelson, Gerald Jay Sussman, Julie Sussman, The MIT Press
4. 구글을 지탱하는 기술, 니시다 케이스케, 멘토르
5. http://en.wikipedia.org/wiki/Fold_(higher-order_function)
6. http://broadcast.oreilly.com/2009/04/an-interview-with-anders-hejls-1.html
7. http://en.wikipedia.org/wiki/Miranda_(programming_language
8. http://www.math.chalmers.se/~rjmh/Papers/whyfp.pdf

Welcome to F#(6) - 비교본능.

F# 2009. 4. 23. 09:27 Posted by 알 수 없는 사용자
-지난시간에 이어서.
잘들 지내셨는지요. 어느새 수요일이 다 지나가는 군요. 오늘은 어제에 비해서 일이 잘 되셨나요? 그렇습니다. 사람은 자연스럽게 비교를 하려는 본능비스무리 한게 있는거 같습니다. 여친과 다른 사람들을 비교하고, 자기 자식이랑 친구아들을 비교하고, 심지어는 별 찌질한것까지도 비교하면서 그 우위에서 오는 만족감을 느끼려는게 사람이 아닐까요.

하지만, 비교는 좋은 이해의 틀이기도 합니다. 뭔가가 틀리다는 건, 다른 것들과 부딪히면서 비로소 틀린점들이 눈에 들어오기 시작하고 형체를 갖추기 시작합니다. 이번 포스트에서는 지난 포스트에서 봤었던 F#예제와 같은 일을 하는 C#예제를 통해서 차이점을 좀 찾아보고자 합니다. ........늘 느끼는 거지만, 참 서두를 번지르르하게 써놓고 시작을 하네요.-_-



- C#코드부터 보자!

넵. 보여드리겠습니다. 보여드려야죠~. 다만, 아래의 코드는 제가 짠 코드일뿐 모범적인 코드라고는 볼 수 없다는 걸 생각하고 봐주시기 바랍니다.


class Demo
    {
        private string destDir;
        private List<STRING> finalFiles;

        public Demo(string destDir)
        {
            this.destDir = destDir;
            finalFiles = new List<STRING>();
        }

        private List<STRING> GetSthAll(Func<STRING, string[]> GetSth, string path)
        {
            return GetSth(path).ToList();
        }

        public void PrintAllFilesCount(string ext)
        {
            GetAllFiles(destDir);
            List<STRING[]> filesWithExt = finalFiles.Select(file => file.Split('.')).ToList();
            int countOfExt = filesWithExt.Where(file => (file.Length == 2) && (file[1] == ext)).Count();

            Console.WriteLine(countOfExt);
        }

        private void GetAllFiles(string destDir)
        {
            finalFiles.AddRange(GetSthAll(Directory.GetFiles, destDir));
            foreach (string currentDir in GetSthAll(Directory.GetDirectories, destDir))
            {
                GetAllFiles(currentDir);
            }
        }
    }

그리고 위 코드는 아래처럼 실행할 수 있습니다.


class Program
    {
        static void Main(string[] args)
        {
            Demo demo = new Demo(@"c:\ATI");
            demo.PrintAllFilesCount("txt");
        }
    }

위의 코드는 이전 포스트의 코드와 같은 결과를 내는 코드입니다. F#에서는 중간결과를 따로 저장하지 않고 |>를 통해서 바로바로 다음함수로 연결했었기 때문에 중간값의 상태저장에 대한 고려가 필요없었습니다. 그 때문에 제귀호출 과정의 데이터를 어디에 저장해야 할지 역시 고려하지 않아도 되었습니다. 즉, 데이터가 어디에서 어디로 가는지에 대한 데이터의 흐름이 추상화 되어 있었던 것이죠. 위의 코드를 보시면, 명령형언어에서 오는 한계점이 분명히 존재하지만, 특별히 복잡해보인다거나 코드가 길어졌다거나 하는 모습은 볼 수 없습니다. 왜그럴까요?

바로 LINQ때문입니다. 함수형언어의 장점을 녹인 LINQ덕분에 데이터추출과정이 추상화되어서 코드가 큰 차이없이 작성될 수 있었습니다. 최근 추세는 명령형언어와 함수형언어가 조금씩 융화되어 가는 것 같습니다. C#에도 점점 함수형언어의 장점과 동적언어의 장점이 녹아들어가고 있습니다. 물론 C#의 설계자인
Anders Hejlsberg는 static한 언어가 가지는 강점을 여전히 높게 평가하고 있습니다만, 필요하다면 점점 융화되어 가는거겠죠. 그럼, 어디한번 계급장떼고(?)...아니아니 LINQ떼고 한번 해보죠!


- 계급장떼고 붙여봐!
넵, 계급장테고 붙여보겠습니다. LINQ없이 C#2.0의 익명메서드 기능을 이용해서 동일한 코드를 작성해 보겠습니다.


class Demo2
    {
        private string destDir;
        private List<STRING> finalFiles;

        public Demo2(string destDir)
        {
            this.destDir = destDir;
            finalFiles = new List<STRING>();
        }

        private List<STRING> GetSthAll(Func<STRING, string[]> GetSth, string path)
        {
            return GetSth(path).ToList();
        }

        private List<STRING[]> GetSplitedList(List<STRING> data, Func<STRING,STRING[]> splitMethod)
        {
            List<STRING[]> splitedList = new List<STRING[]>();
            foreach (string str in data)
            {
                splitedList.Add(splitMethod(str));
            }
            return splitedList;
        }

        private int GetFilesCountWithExt(List<STRING[]> data, Func<STRING[], bool> filterMethod)
        {
            List<STRING[]> filteredList = new List<STRING[]>();
            foreach (string[] fileStr in data)
            {
                if (filterMethod(fileStr))
                {
                    filteredList.Add(fileStr);
                }
            }
            return filteredList.Count;
        }
        public void PrintAllFilesCount(string ext)
        {
            GetAllFiles(destDir);
            List<STRING[]> filesWithExt = GetSplitedList(finalFiles, delegate(string str) { return str.Split('.'); });
            int countOfExt = GetFilesCountWithExt(filesWithExt, delegate(string[] fileStr) { return (fileStr.Length == 2) && (fileStr[1] == ext); });
            Console.WriteLine(countOfExt);
        }

        private void GetAllFiles(string destDir)
        {
            finalFiles.AddRange(GetSthAll(Directory.GetFiles, destDir));
            foreach (string currentDir in GetSthAll(Directory.GetDirectories, destDir))
            {
                GetAllFiles(currentDir);
            }
        }
    }

어떻습니까? 이젠 차이가 좀 드러나기 시작합니다. 익명메서드를 이용해서 람다를 흉내냈지만, 데이터추출과정을 추상화하진못해서 그 추출과정의 중간값저장이나 추출루프를 직접 제어해줘야 합니다. 이렇게 되면, 문제가 조금씩 복잡해질수록 코딩도 어려워 지겠지요.(Func<>는 .NET 3.5에서 추가된거지만 봐주셈요-_-)

즉, 단순히 LINQ라는 기술을 쓰고 안쓰고정도의 문제가 아니라, 추상화 단계가 낮아지는 것이기 때문에 생각의 복잡도는 높아질 수 밖에 없는 것입니다. 데이터의 흐름을 추상화해주는 함수형언어의 특징이 이런부분에서 굉장한 강점으로 작용하게 됩니다.



- 할말다했냐?
네 별로 대단치않은 예제이지만, 최선을 다해서 한번 비교를 해봤습니다. 물론, 예제가 완전히 적절한건 아니고 크기나 복잡도 역시 높은 수준이 아니라 명확한 비교라고 볼 수는 없지만, 하나의 예제가 될 수 있다고 생각합니다. 이번 포스트에서 함수형언어인 F#의 장점을 못 느끼셨다면 모두 제 책임이나 따뜻한 질타를 주시기 바랍니다. 끝으로 Expert F#의 저자인 Don Syme의 블로그에 올라온 사례하나를 소개하고 마치겠습니다.

-포스트 원문-

The first application was parsing 110GB of log data spread over 11,000 text files in over 300 directories and importing it into a SQL database. The whole application is 90 lines long (including comments!) and finished the task of parsing the source files and importing the data in under 18 hours; that works out to a staggering 10,000 log lines processed per second! Note that I have not optimized the code at all but written the application in the most obvious way. I was truly astonished as I had planned at least a week of work for both coding and running the application.

Ralf Herbrich, Microsoft Research
(http://blogs.msdn.com/dsyme/archive/2006/04/01/566301.aspx)


-제 맘대로 해석-_-;;;-

첫번째 프로그램은 300개가 넘는 디렉토리에 있는 11,000개의 텍스트파일에 저장되어있는 100GB나 되는 로그 데이터를 가져와서 분석해서 SQL데이터베이스에 저장하는 거였습니다. 어플리케이션은 전체 길이가 주석을 포함해서 딱 90줄이었고, 로그파일들을 읽어들여 분석하고 저장하는데 18시간이 채 안걸렸습니다. 1초에 1만줄의 로그를 처리한 셈이죠! 더 중요한건 코드를 최적하려고 하지도 않았고 그냥 제일 명확하게 보이도록 짰습니다. 제가 진짜 놀랐던건, 솔직히 전 그 프로그램 짜고 돌리는데 일주일은 걸릴 줄 알았었거든요.



- 참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS

2. http://blogs.msdn.com/dsyme/archive/2006/04/01/566301.aspx


 

Welcome to F#(5) - 아주 조금씩 심화되는 탐색전.

F# 2009. 4. 20. 08:50 Posted by 알 수 없는 사용자

- 벌써 다섯번째네?


네, 맞습니다;; 벌써 다섯번째 포스트입니다. 그런데 영~ 포스트는 나아질 기미를 보이진 않는군요. 모든게 제 잘못입니다;; 계속 노력중이니 좋게 봐주시길 부탁드립니다.^^ 오늘은 지난번에 했던 거 보다 조금 더 복잡한 코드를 가지고 이야기를 해보려고 합니다. 그리고 그 코드를 바탕으로 C#과의 비교를 통해서 좀 더 의미있는 것들을 이야기 해보고 싶습니다. 읽어보시고 의미가 없다면 따뜻한 피드백으로 질타를...;;

 

- 알았으니까 예제부터 보자!

open System.IO

let rec getAllFiles dir =
    List.append 
      (dir |> Directory.GetFiles |> Seq.to_list) 
      (dir |> Directory.GetDirectories |> Seq.map getAllFiles |> Seq.concat |> Seq.to_list)
 
//여기서는 그렇게 가져온 파일리스트를 '.'을 기준으로 짤라낸다.
let splitedAllFiles = getAllFiles @"C:\ATI" |> List.map (String.split ['.'])
 
//여기서는 각 파일을 돌면서, 확장자가 사용자가 원하는 확장자인거만 걸러내고, 개수를 리턴한다.
let Count ext = splitedAllFiles |> List.filter (fun file -> if file.Length = 2 then file.[1] = ext else false) |> List.length

 

그리고 위의 예제를 F#인터렉티브에서 확인해본 결과입니다.(PowerPack.dll을 로딩하는 부분빼곤 전부 Alt+Enter를 이용했습니다.)


 > #r "FSharp.PowerPack.dll";;

--> Referenced 'C:\Program Files (x86)\FSharp-1.9.6.2\bin\FSharp.PowerPack.dll'

>

 

val getAllFiles : string -> string list
val splitedAllFiles : string list list
val Count : string -> int


> Count "txt";;
val it : int = 129
>

 

위 코드는 명시된 디렉토리를 모두 훑어서 거기에 있는 파일들의 경로를 가져오고, 그 파일들 중에 사용자가 명시한 확장자를 가진 파일이 몇개나 되는지를 출력하는 코드로 Expert F#에 나온코드를 조금 수정하고 덧붙인 것입니다. 일단 이 예제를 가지고 C#과의 비교를 통해서, 함수형언어로서의 F#이 가지는 장점에 대해 조금알아보고자 합니다. 그럼 우선 위의 코드에 대해서 알아보겠습니다.

 

- 첫번째는?

우선, 위의 코드에서는 두가지의 자료구조가 사용되었는데요, Seq과 List가 그것입니다. Seq는 C#에서의 IEnumerable<>타입을 의미하구요, List는 List<>을 의미합니다. 특성도 비슷합니다. 조금 더 익숙하실 C#을 통해 먼저 알아보죠. LINQ에서 보면, 쿼리를 날렸을때,  IEnumerable<>타입이 리턴되는데요, 이 IEnumerable<>로 리턴된 시퀀스는  아직 데이터를 가지고 있는게 아닙니다. 실제로 IEnumerable<>의 각요소에 접근해야 할때가 되서야 LINQ의 쿼리가 실행됩니다. 예제코드를 먼저 보시져~!

 

class Program
    {
        static void Main(string[] args)
        {
            int[] list1 = new int[] { 98, 34, 5, 134, 64, 57, 99, 320, 21, 55, 62, 39 };
            IEnumerable<int> queriedList1 = list1.Where(num => num <= 100);

            foreach (int num in queriedList1)
            {
                Console.WriteLine(num);
            }

            list1[0] = 198; //98 -> 198
            list1[1] = 134; //34 -> 134

            Console.WriteLine("---------------second call-------------");

            foreach (int num in queriedList1)
            {
                Console.WriteLine(num);
            }
        }
    }

 

위코드를 보시면, int형 배열에 대해서 100보다 작은 수만 골라서 쿼리를 날려서 IEnumerable<int>형으로 리턴을 받습니다. 그리고 처음에 모든 수를 돌면서 출력을 하고, 그 다음에 쿼리의 대상이었던 배열의 값중에 처음 두개를 100보다 크게 바꾼뒤에 다시 시퀀스돌면서 숫자를 출력하고 있습니다. 여기서 주목하실건, 쿼리를 두번 날린게 아니라 그저  IEnumerable<int>시퀀스 안의 요소를 한번더 돌면서 출력한 것 뿐이라는 겁니다. 결과를 보시죠.

 

 

위의 결과를 보시면, 두번째 foreach에서는 바꿔준 값들이 나오지 않는 걸 알 수 있습니다. 즉, 쿼리는 만들어지긴 하되 그 실행시기는 직접 요청될때로 연기(Deferred)된 것입니다. 이런 IEnumerable의 특성으로 인해, 변경된 내용을 가져오는데 쿼리를 두번날릴 필요가 없이 그저 전체요소를 한번더 돌기만 하면 자연스럽게 변경된 내용을 읽어오는게 되는 것입니다. 이와는 반대로 List는 즉시 모든 값을 읽어옵니다. 그래서 위의 코드에서,

 

IEnumerable<int> queriedList1 = list1.Where(num => num <= 100);

위코드를 아래와 같이 수정하고,

List<int> queriedList1 = list1.Where(num => num <= 100).ToList();


다시 예제를 돌려보면, 아래와 같은 결과가 나오는 걸 확인할 수 있습니다.

 

 

즉, 이미 수행시점에서 모든 요소를 미리(Immediately)가져 왔기 때문에, 원본값이 수정된 뒤에서 쿼리로 가져온 값에는 변화가 없는 것이죠. 이런 특성은 F#에서도 여전히 유효합니다.

 

> seq { 1 .. 100000000 };;
val it : seq<int> = seq [1; 2; 3; 4; ...] 
 

F# 인터렉티브에서는 위와같은 결과가 나옵니다. 즉, 1부터 1억까지의 숫자의 시퀀스를 선언하면, 모든 숫자가 다 즉시 만들어지는게 아니라는 거죠. 결과에서 보듯이 1부터 4까지만 나온걸 보실 수 있습니다. 즉, F#인터렉티브가 네번째 요소까지만 미리 읽어오도록 하고 있는거지요. 그래서 주의 하셔야 할점도 있습니다. List는 미리다 읽어오므로 메모리에 다 올라갈 수 있을지를 미리 생각해봐야 하고, Seq는 미리 다 읽어오는게 아니므로 읽어오기 전에 데이터변경이 되면 다른 결과가 나올 수 있습니다. 이 부분을 유념해야 합니다.

 

- 두번째는?


그럼 맨 처음에 파일들을 읽어오는 코드를 계속 보기로 하죠. 위에서 "|>"라는 기호가 보이는데 이 건 뭘까요? 뭐 리눅스같은데서 shell을 좀 다뤄보셨거나 하면 pipe겠구나 하는건 금방 아실 수 있겠죠.^^;; 이건 정방향 파이프 연산자(forward pipe operator)라고 하는데요, |> 앞쪽의 리턴값을 |> 뒤쪽의 메서드에 넘겨주는 역할을 합니다. 즉 위의 예제를 보자면, Directory.GetFiles의 인자로 dir를 넘겨주고, 그 결과를 Seq.to_list 의 인자로 넘겨주는 거죠. 굉장히 심플하게 중간 결과를 어디에 담아둘지 고민하지 않고 함수들을 연결할 수 있게 도와주는 연산자입니다.

아, 그리고 여기서 주목하실 부분이 있다면, 닷넷프레임워크의 메서드인 Directory.GetFiles나 Directory.GetDirectories를 F#에서도 First-class function으로 사용할 수 있습니다. 이런 부분이 F#의 진정한 강점이 아닌가 싶네요. 그리고 기존의 C#이나 VB.NET을 이용하시던 분들이 좀더 자연스럽게 F#이용해서 효율적인 함수형 프로그램을 작성할 수 있게 해주는 원동력이기도 하구요.


- First-class function?


Fisrt-class function이란 함수형언어의 특징중 하나로서, 함수가 다른 함수의 파라미터로 들어갈 수 있고, 연산결과로 리턴될 수 있으며 명령형언어에서는 추가적으로 변수에 대입가능한(자바스크립트를 떠올리시면 됩니다.) 함수라는 걸 의미합니다. Higher-order function은 함수를 인자로 받거나 함수를 결과로 리턴하는 함수를 의미하구요.

 

그 다음줄도 역시 Directory.GetDiretories에 dir을 넘겨줘서 현재 디렉토리의 하위 디렉토리들의 시퀄스를 가져오고 그걸 Seq.map에 넘겨줘서 각 디렉토리별로 getAllFiles를 실행해서 모든 하위 디렉토리의 파일경로를 가져오도록 합니다. 그리고 그 결과를 Seq.concat을 통해서 하나의 시퀀스로 합치구요, 그 다음에 그 시퀀스를 list로 변환합니다. 그리고 모든 결과를 하나의 list로 합쳐서 리턴합니다.

 

그리고 splitedAllFiles에서는 그렇게 만든 리스트의 모든 요소를 대상으로 '.'을 기준으로 파일경로와 확장자로 분리합니다. 그리고 마지막 Count함수에서는 그 결과를 List.filter함수에게 넘겨줘서 확장자가 사용자가 명시한 확장자와 일치하는 것만 추려서 하나의 리스트로 만든뒤, 그 리스트의 길이를 리턴합니다. 이렇게 해서 해당 디렉토리와 그 하위 디렉토리에서 특정 확장자를 가지는 파일의 개수를 읽어오는 프로그램이 완성되는 것입니다.

 

 

- C#과 비교한대매?


이번 포스트에서는 F#의 예제코드를 통해서 F#의 Seq, List의 특성을 알아봤고, |>연산자를 통해서 함수들을 쉽게 연결하는 것을 보았습니다. 다음 포스트에서는 이 F#의 코드와 동일한 C#코드를 통해서 비교를 조금 해보고자 합니다. 글의 내용이 여전히 만족스럽진 않지만, 저도 부족한 머리 박아가면서 쓰는 글이니 좀 봐주시구요;; 따뜻한 피드백 부탁드립니다.

 

- 참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS

2. Pro LINQ: Language Integrated Query in C# 2008, Joseph C. Rattz, Jr. , APRESS.

3. Programming Language Pragmatics 2nd Edition, Michael L. Scott, Morgan Kaufmann Publishers


Welcome to F#(4) - 과거와 배경을 좀 더 알고싶어.

F# 2009. 4. 18. 21:48 Posted by 알 수 없는 사용자

- I remember back in the day

우리는 어떤 사람이 마음에 들면, 그 사람에 대해서 작은 거라고 할지라도 더 알고 싶어서 발버둥 칩니다. 멀리 갈것도 없습니다. 연예인에 삘꽂혀서 그 연예인의 나이, 취미, 혈액형 그리고 별 정확하지도 않은 키랑 몸무게 찾아다니느라 애쓰던 시절을 떠올려 보시지요. 그리고 노트북을 살때, 노트북에 꽂혀서 사소한 것 까지도 다 찾아내고 물어보고 알아내서 자신에 마음에 조금이라도 더 드는 상품을 사지요.
 
아이팟도 그 좋은 예인데요, 아이팟을 사고나서 온갖 해킹이며, 어플다 깔다가 하루가 지나갔던 그런 경험을 하신분도 있을 겁니다. 서론이 너무 기네요. 이번 포스트에서는 함수형언어를 이해하려고 무진장 애를 쓰다가 머릿속에 떠오른 게 있어서 적어보려고 합니다. 엄밀하지 못한 글일 수도 있으나, 따뜻한 피드백 많이 부탁드립니다. 차가운 피드백은... 아시져? ^^
 

- 기초학문?

개인적인 생각으로 우리가 흔히 기초학문이라 부르는 것들은 공통적인 특징이 있는 것 같습니다. 그리고 기초학문은 아마도 존재하지 않던 새로운 것들을 만들어 내는 쪽보다는 기존에 있던 것들에 새로운 의미를 부여하고 새로운 발견을 해나가는 쪽이 더 맞지 않나 합니다. 그래서 그 새로운것이 어떤건지 성질을 밝혀내고 알아내서 어떻게 응용이 될 수 있을지에 대한 단서를 제공하는게 아닌가 합니다. 

- 그래서 뭐? 수학?

그런 기초학문중에 대표적인 하나를 꼽자면 수학을 꼽을 수 있을 것 같습니다. 수학이라는 학문도 사람들이 살면서 자연스럽게 뭔가를 세기 위해서 군만두만 먹으면서 벽에 작대기를 그으면서 표시하던게 점차 발전하여 지금의 우리가 쓰는 숫자도 아랍쪽 어디선가 발견이 되고  퍼지면서 사용되는거고, 그 도구에 '수'라는 이름을 붙이면서 시작된게 아닌가 합니다. 뭔가 분명히 존재하지만 명확하게 정의할 수 없을때, 거기에 이름을 붙이게 되면 우리는 그에 대한 연구를 시작할 수 있게 됩니다. 이름을 붙인다는 건 우리에게 상당한 지배력을 제공해 주기 때문입니다. 무진장 가깝지만 사귀자고 누구도 명확하게 이야기 하지않은 상태라면, 서로 뻘쭘하고 넘을 수 있는 선의 범위도 모호하지만 서로의 관계를 공식적으로 '연인'이라고 이름붙이게 되면 더 자연스럽게 애정행각(!)을 벌일 수 있는 법이지요. ^^;; 

- 그래서 하고 싶은 이야기가 머꼬?

자, 다들 중~고등학교때 수학공부를 하고 문제를 풀던 때를 떠올려 보시지요. 수학은 정의와 공식으로 시작해서 정의와 공식으로 끝납니다. 정의나 공식은 이미 존재하는 무언가를, 그리고 그 무언가에 나타나는 패턴을 명확하게 나타내 보일 수 있는 수학적 도구입니다. 그리고 그런 정의와 공식에서 시작해서 또다른 정의와 공식이 그위에 쌓이게 되고 그런 것들이 계속 되면서 각각의 수학분야가 하나로 모이게 되는 대통합수학도 이루어 지는 거고 그렇게 하다보니 페르마의 마지막 정리 같은 난제도 해결이 될 수 있었습니다. 

즉, 수학문제를 풀때 그 문제를 이루고 있는 각 요소가 뭔지 공식으로 정의를 해내고, 그 정의를 모아서 문제에 대한 공식을 도출해 냅니다. 그리고 그 공식을 통해서 문제를 해결하고, 동일한 패턴을 보이는 문제들을 풀어나갑니다. 가만히 생각해보면, 중간중간에 도출되는 값을 x,y같은 부호에 묶어주고, 도출된 공식을 부호로 표시하거나, 공식에 있는 부호를 공식으로 풀어내서 문제를 해결하곤 했습니다. 수학문제를 해결한다는 건 무엇을 어떻게 정의하느냐에 따라서 금새 풀리기도 아무리 싸매고 고생해도 안풀리곤 했던 거지요. 우리가 풀던 수학에선 x에는 하나의 값이나 공식만 정의할 수 있었고, 상태를 저장하는 거 역시 없었습니다. 

- 그게 함수형 언어랑 뭔 상관?

최대공약수를 정의할 때 명령형 프로그래머는,

a와 b의 최대공약수를 계산 해내려면, 일단 a랑 b가 같은지 보는거야. 만약에 그렇다면, 둘중에 하나를 출력하고 끝나면 되는거지. 안 그렇다면, 둘중에 큰수를 두수의 차(빼기)로 바꾸고 다시 하면 돼

그리고 함수형 프로그래머는,

a와 b의 최대공약수는 a와 b가 같다면 a이고, a와 b가 같지 않다면 c와 d에 대한 최대공약수를 구하게 되는데 여기서 c는 a와 b중에 작은수가 되고, d는 그 두수의 차가 된다. 주어진 쌍의 숫자의 최대공약수를 구하려면, 이 공식을 세부적으로 계속 확장해 나간다음에 식이 끝날때까지 하나씩 수행하면서 줄여나가면 된다.

사실 이 글을 쓰게 된데에는 "Programming Language Pragmatics"에서 위의 문장을 보고서 든 생각을 바탕으로(제 맘대로 해석했습니다-_-) 기존에 수학에 대해서 생각해오던 것들을 추려서 쓴 것입니다. 위의 문장이 많은 것을 말해주는 것 같다는 생각이 듭니다. 명령형 프로그래머는 "어떻게"에 집중하는 반면에 함수형 프로그래머는 최대공약수 "무엇"인지를 정의하는데 중점을 둔다는 것입니다. 물론 F#은 Haskell과 같은 순수한 함수형언어는 아닙니다. 물론, 순수한 함수형 언어로도 사용할 수 있지만, 그건 F#의 역할이 아니라는게 개인적인 생각입니다. 

- 아 거 말 되게 많네.

깊이도 없는 내용을 쓸데없이 길게말하느라 쓰는 저도 수고했고, 읽어주신 여러분도 수고하셨습니다.-_-;; 사실 함수형언어는 "왜 함수형 언어인가?"에 대한 질문에 답을 할 수 있어야 함수형언어와 친하게 지낼 수 있지 않을까 하는 생각이 듭니다. 명령형나라의 프로그래머와 함수형나라의 프로그래머는 서로 문제를 바라보는 관점이 다르기 때문이죠. "Why Functional Programming Matters?"와 같은 좋은 논문도 있어서 공부중이지만, 부족한 제머리와 부족한 시간은 깝깝하기만 하군요;; 아무튼, 제가 느끼는 것들은 이 공간을 통해서 계속 머리박아가면서 쓰겠습니다. 좋은 피드백으로 격려해주세요. 앞으론 차가운 피드백 이야긴 안하겠습니다.^^ 


-참고자료

1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS.
2. Programming Language Pragmatics, 2nd Edition, Michael L. Scott, Morgan Kaufmann Publishers
3. 거짓의 사람들, 스캇 펙, 비전과 리더십
4. 페르마의 마지막 정리, 사이먼 싱, 영림카디널
5. 사고력을 키우는 수학책, 오카베 츠네하루, 을지외국어



ps. 왜 연재 계획도 없고, 이야기 했던 계획이랑도 틀린 포스트가 계속 올라오냐?

아직 아무도 지적하신 분은 없지만... 네 지당하신 지적입니다. 제가 연재라는 개념이 희박하고 분야에 대한 지식이 얉은 탓에 그렇게 되는 점 혹시 안좋게 보신 분들이 있다면 사과드립니다;;; 앞으로도 계속 그럴지도 모르기 때문에 지금쯤에는 사과를 해 드려야 나중에 한꺼번에 욕을 먹는 사태를 방지할 수 있을거 같아서 말이죠-_-;;;


Welcome to F#(3) - 사소한 탐색전.

F# 2009. 4. 12. 22:10 Posted by 알 수 없는 사용자

-관심있으면 많이 알아봐야 하는 법.
이제 F#과의 만남도 어느덧 세번째에 접어들었네요. 한 두번쯤 만나면 상대방에 대해 관심이 있는지 없는지 스스로 눈치챌 수 있겠죠. 그리고 관심이 있다면, 더 열심히 상대방에 대해서 알려고 노력하고 있을테구요. F#에 대해서 알려고 노력하다보니, F#의 4차원적인 면을 하나발견하고 잠깐 당황해서 허우적댔습니다. 거기에 대해서 짧게 적어보려 합니다.

-짧은 예제를 통해 까발려본 F#의 4차원적 특징

#light

type TwoNumbers =
    {Number1 : int; Number2 : int}

let num = {Number1 = 3; Number2 = 5}

printfn "%d %d" num.Number1 num.Number2

그리고 실행해보면 아래와 같은 결과가 나온다.


음~ 예상했던 대로 결과가 잘 나왔습니다. 그런데 왠지 타입선언과 그 타입을 실행하는 코드는 다른 소스파일에 넣는게 맞는거 같아서 타입선언을 TwoNumbers라는 파일을 생성해서 거기에 넣자는 생각이 들었죠. 그래서 C#에서 하던것 처럼 새로운 파일을 생성해서 거기에 타입선언 코드를 집어넣고, 다시 자신있게 컴파일을 실행했습니다. 그런데! 다음과 같은 오류가 발생하더군요.

오류 1 - The record label 'Number1' is not defined.

오류 2 - Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved

오류 3 - Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved



즉, Number1이랑 Number2가 선언되어 있는 타입을 찾을 수 없다는 말인데요. 이게 무슨 소리입니까;; 같은 솔루션에 있는 파일에 클래스의 선언을 옮겼는데 못 찾다니요..? 그런데, VisualStudio의 한지붕아래에서 살아서 전혀 눈치를 못채고 있었는데, F#의 솔루션 탐색기에는 다른 언어들과는 다른 뭔가 특별한게 있더군요. 이런 4차원 F#!!!

바로 위 그림에서 볼 수 있는 Move Up, Move Down, Add Above, Add Below말인거죠. 무슨 아이돌 그룹이라도 되는 마냥 4인조가 나란히 모여있죠. 이건 당췌 뭘까요? 아! 혹시 F#은 위에서 부터 아래로 차례대로 파일들을 컴파일 하는걸까요? 그럼 타입선언이 있는 TwoNumbers파일을 Move Up해서 다시 컴파일을 해보죠!! 자신있게 컴파일을 했건만 결과는 똑같은 에러가 납니다. 연애를 해봤거나, 작업을 절박하게 걸어본 사람은 그 사람의 성격을 알았다고 생각해서 그에 맞춰주려다가 오히려 더 곤혹스러웠던 상황이 있을겁니다.

이런. F#은 도대체 왜 이렇게 특이한걸까요? 그래서 F#을 잘아는 사람들에게 정보를 얻어보니, F#은 위에서 부터 차례대로 컴파일을 하는 건 맞는데, 그 파일들이 자동으로 하나의 모듈단위로 컴파일이 되고, 그 모듈을 이용하려면, open을 이용해서 그 모듈의 내용을 열어야 사용이 가능하다는 거 였더군요. 그래서 아래와 같이 모듈의 이름을 open해주었습니다.


#light

open TwoNumbers

let num = {Number1 = 3; Number2 = 5}

printfn "%d %d" num.Number1 num.Number2

이제 컴파일이 잘 됩니다.>_<! 모듈의 이름은 명시적으로 아래와같이,

module MyCompany.MyLibrary.MyModule

(위와 아래의 선언은 동일하다)


namespace MyCompany.MyLibrary
module MyModule =
    .......

이렇게 지정해주지 않는이상 파일이름과 동일하며, 무조건 맨 앞의 문자는 대문자가 됩니다.(즉 파일의 이름이 twoNumbers 였더라도, 모듈의 이름은 TwoNumbers가 된다는 말)

-그냥 추가로 이야기하는 공간-

type Card =
    { Number: int; Shape: int }

이렇게 타입(클래스)를 선언하고, 위 선언을 F# Interactive에서 평가하고 아래와 같은 결과가 나옵니다.

type Card =
  {Number: int;
   Shape: int;}

그리고 아래처럼 계속 평가식을 입력해보면....

> let v = {Number = 3; Shape = 5};;

val v : Card

> let a = {a = 4; b = 5};;

  let a = {a = 4; b = 5};;
  ---------^^
stdin(4,10): error FS0039: The record label 'a' is not defined.

> v;;
val it : Card = {Number = 3;
                 Shape = 5;}
>

즉, 타입이름을 명시하지 않아도 멤버의 이름과 타입을 보고 알아서 타입을 찾아서 생성한다는 겁니다. 그래서 a와 b라는 멤버로 생성하려고 했을때, 그런 멤버를 가진 타입이 없어서 에러가 발생하는 거죠.


-어디까지 4차원일까?
F#은 알면 알수록 4차원인거 같네요. 아... F#에 대해서 더 잘 알게되면 F#의 마음을 얻고 잘 지낼 수 있을까요? 일단 최대한 노력해보는 수 밖엔 없겠군요. 이렇게 짧은 탐색전을 마치겠습니다.

-참고자료
1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS.
2. http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec2.aspx#_Toc207785764

Welcome to F#(2) - 두번째 만남.

F# 2009. 4. 8. 23:40 Posted by 알 수 없는 사용자

-애프터
첫번째 만남이 끝난뒤에, 어떻게 애프터를 이어나가야 할지 무처이나 고민했습니다. 첫만남이야 가볍게 서로의 얼굴도 보고, 성격도 맛보는 정도에서 끝난다지만 애프터에서는 서로에 대해서 천천히 자세히 알아가야 할텐데, 갑자기 참여중인 프로젝트의 상황이 악화되서 시간이 잘 나지 않고, 이해부족과 실력부족이 겹쳐서 어떻게 글을 계속 써나가야할지 고민이 많았습니다. 그러다가 직장동료에게 물어봤습니다.

"만약에 튜토리얼을 쓴다면, 어떻게 쓰는게 좋을까?"
"글쎄, 나는 그런게 좋던데. 왜 작은 예제에서 시작해서 하나씩 추가되어 가면서 설명해주는 그런거 있잖아."

이거다 싶었습니다. 그래서 제 내공이 받쳐줄지는 모르겠지만, 최대한 예제를 통해서 F#과의 애프터를 진행해볼까 합니다. 언제나 그렇듯이 제 글에 어색하거나 잘못된 내용이 있을수 있으니 언제나 따뜻한 피드백으로 격려해주시기 바랍니다.(말씀드렸듯이 차가운 피드백은 바로 반사-_-) 그럼 일단 애프터 장소로 가서 F#을 좀 더 만나보도록 하죠~.

-F#에서는 타입을 안쓰는거 같더라?
일단 F#에서 타입을 결정하는데 쓰이는 타입유추(Type Inference)에 대해서 알아보겠습니다. 타입유추는 F#에서만 쓰이는 개념은 아닙니다만, 저 같이 함수형언어에 친숙하지 않은 경우에는 생소함을 느끼게 됩니다. 우선 아래와 같은 예제를 작성해서 아래 두줄을 드래그 해서 F# Interactive로 보내보죠. F# Interactive를 띄우는 방법과 F# Interactive로 보내는 방법은 잊지 않으셨죠? 잊으셨다면, 이전 포스트를 참조해주세요~.

#light

let count = 1
printfn "%d" count


실행결과에 대해서 이야기 하기전에 #light에 대한 설명을 조금 드리자면, 지난 포스트에서 말씀드린 것 같이 이 지시어는 F#에서 좀더 간략한 문법을 사용할 수 있도록 해줍니다. 그런데, 이런 키워드를 명시적으로 꼭 선언해야 하는 이유는 다음과 같습니다. F#은 OCaml으로 부터 영감을 받아서 설계되고 핵심적인 부분중에 일부분을 공유하고 있씁니다. 그리고 OCaml의 코드를 컴파일할 수 있는 기능을 갖추고 있는데, 간단한 OCaml코드의 경우는 수정없이 컴파일 가능하게 되어있습니다. 그래서 그 경우를 위해서 #light라고 명시해줘야만 F#의 간단한 문법이 적용되는 것입니다.

그러면, 다시 소스코드로 넘어가서 그 소스의 결과는 아래와 같습니다.

val count : int
1
 
즉, 한줄씩 평가 돼서 그 결과가 나오는 건데요, 첫번째 줄은 count라는 value를 선언했고 그 타입은 int라는 말입니다. 그리고 그 다음줄은 count의 값을 출력한 결과인거죠. 눈치 빠른 분이라면, 아마 여기서 뭔가를 눈치채셨을 겁니다. 타입을 정해주지 않았는데, count의 타입이 int라는걸 문맥을 통해 유추해냈습니다. 그리고 아래의 printfn의 출력패턴을 보면 "%d", 즉 C와 동일하게 정수형값을 출력하겠다는 말인데요, 앞의 평가식에서 count가 정수형이라는 걸 유추해냈기 때문에 에러없이 1을 출력하고 프로그램을 종료합니다. 이런걸 타입 유추라고 부릅니다.(원문으로는 Type Inference인데, inference에 대한 딱히 다른 용어를 찾지 못해서 일단 유추라고 하겠습니다.-_-)

타입유추는 코드를 분석해서 제약사항들을 모아서 이루어집니다. 여기서 제약사항이란, 매개변수가 가질 수 있는 타입을 명시해줌으로써 제약을 가하는 개념인데요, 문맥에서 타입을 유추할때 +,-,*,/같은 경우는 기본적으로 매개변수를 int타입으로 유추됩니다. 그리고 아래의 예제들을 F# Interative에 평가해보죠.

let printInt x = printfn "%d" x

let printString x = printfn "%s" x

그러면, 아래와 같은 결과가 나오죠.

val printInt : int -> unit

val printString : string -> unit

즉, 문맥상 정수형을 출력하니까 x는 int형이 된거고, 문맥상 스트링을 출력하니깐 x는 스트링으로 유추된 거죠. 그리고 아래의 예제를 더 보시죠.(그리고 참고로 unit은 많은 분들이 기대하시는 것 처럼 unsigned int는 아니구요, 정확하게 같진 않지만 void를 나타내는 F#의 타입입니다.-_-)

let plusplus x = (x + x)
let plusplus2Times (x) = plusplus x + plusplus x
-----결과----
val plusplus : int -> int
val plusplus2Times : int -> int

그리고 아래와 같이 바꿔서 다시 평가해보죠.

let plusplus x = (x + x)
let plusplus2Times (x:float) = plusplus x + plusplus x
-----결과----
val plusplus : float -> float
val plusplus2Times : float -> float

역시 사용되는 문액에 따라 plusplus를 호출하는 plusplus2Times에서 int형 x를 넘겨주면, plusplus의 매개변수도 int타입이 되고, float을 사용하겠다고 타입에 제약을 정해주면(즉, 매개변수 x가 될 수 있는 타입에 명시적으로 제약을 주는 것) 그에 따라서 plusplus에 넘어가는 매개변수와 리턴타입역시 그에 따라 float타입으로 유추되는 것을 볼 수 있죠. 즉, F#에서는 타입을 명시해줄 수도 있지만, 이런 작업을 직접 해주지 않으면 기본적으로는 타입유추를 통해 타입을 알아내고 적용한다는 겁니다.

-타입유추라는거 왠지 불안해보이는데?
이런 생각을 해볼 수 있습니다. '과연 이렇게 컴파일러가 타입을 추측해주는게 무슨 장점이 있는거냐?' 이 포스트에서 관련된 내용을 많이 찾아볼 수 있었습니다. 저자는 타입유추의 광팬이로군요. 언제든지 C#같은 언어에서 제공하는 var타입이나 익명메서드등을 사용해서 자기 대신 컴파일러가 타입에 대한 문제를 다루도록 한답니다. 즉, '타입유추는 타입안정성을 해치지 않고 그저 컴파일러가 대신 타입을 찾을뿐이다', '타입을 일일이 적지 안아도 되니깐 리팩토링시에도 편리하다', '타이핑을 줄일 수 있다'랍니다. 그리고 반론도 만만치 않습니다. 아주 긴 댓글로 서로 의견을 주고 받고 있군요. 즉, 한마디로 좋다 나쁘다를 정할 수 없는 취향의 문제인것 같기도 합니다.(우주 끝날때까지도 결론이 안 날문제들이 바로 취향에 관련된 문제죠-_-)

-F#은 어떤 타입이야?
그리고 타입이야기가 어쩌다 새어나왔으니, 타입을 한번 적어보겠습니다.
 타입  예  설명
 int  int  그냥 32비트 정수입니다.
 type option  int option, option<int>  선언된 타입의 값이 있거나, 혹은 값이 없는 걸 의미하는 None을 가집니다. Optional parameter와 비슷한 개념입니다.
 type list  int list, list<int>  선언된 타입의 immutable한 값들의 linked list입니다. 리스트의 각 값들은 [5;2;88]처럼 같은 타입을 가져야 합니다.
 type1 -> type2  int -> string  함수타입입니다. 즉, type1을 받아서 결과값으로 type2를 리턴한다는 거죠.
 type1 * ... * typeN  int * string  한쌍(a pair), 두쌍 그리고 그 이상의 타입들의 조합이 가능한 튜플타입입니다. (1, "3")같은 경우 말이죠.
 type []  int[]  배열타입이죠. 1차원이고, 고정된 크기의 mutable한 값들의 모음입니다.
 unit  unit  하나의 값을 나타내는 ()을 나타내는데, 명령형 언어의 void와 비슷한 의미입니다.
 'a, 'b  'a, 'b, 'Key, 'Value  제네릭하게 어떤 타입이든지 올 수 있는 변수타입입니다.

Expert F#에 나오는 예제를 마지막으로 보겠습니다.

 String.split [' '] "hello world"

 String.split ['a';'e';'i';'o';'u'] "hello world"

일단 위의 코드를 F# Interactive에 보내서 평가해보면 "The value, constructor, namespace or type 'split' is not defined. A construct with this name was found in FSharp.PowerPack.dll,......"이런 에러가 뜹니다. 즉, String클래스에 split이라는 값이나, 생성자, 네임스페이스 혹은 타입이 존재하지 않는데, FSharp.PowerPack.dll에서 발견이 되었다는 친절한 메세지 입니다. CTP버전으로 오면서 많은 기능이 FSharp.PowerPack.dll로 옮겨졌기 때문입니다. String은 Microsoft.FSharp.Core.String을 참조하는데요 Microsoft.FSharp안에 있는 Core,Collection,Text,String같은 네임스페이스는 String.split이나 open String같이 한 단어로 참조가 가능하고 합니다. 이럴땐, FSharp.PowerPack을 참조추가해줘야겠죠. 참조추가후에 open 지시어를 통해서 해당 네임스페이스의 내용을 알아내야 하는데, F# Code에선 기본적인 Core나, Operators, Collections같은 네임스페이스는 암시적으로 open을 한다고 합니다. 그리고 비주얼 스튜디오가 아닌 F# Interactive에서 참조추가를 하려면, 아래 처럼하면 됩니다.

 > #r "FSharp.PowerPack.dll";;

--> Referenced 'C:\Program Files\FSharp-1.9.6.2\bin\FSharp.PowerPack.dll'


그리고 다시 F# Interactive로 보내서 평가해보면 아래와 같은 결과나 나옵니다.

 val it : string list = ["hello"; "world"]
>
val it : string list = ["h"; "ll"; " w"; "rld"]

이 예제에서 ' ', 'a'는 그냥 문자고, "hello world"같은건 문자열, ['a';'e';'i';'o';'u']는 문자의 리스트, ["hello"; "world"]는 문자열의 리스트라는 걸 알 수 있습니다.

-애프터의 만족도는?
변명이지만, 시간도 조금은 부족했고, 아는것도 부족하다보니 포스트의 내용이 썩 만족스럽지는 못한 것 같습니다. 애프터가 만족스럽지 못하셨다면, 제탓이 가장크겠군요-_-. 그래도 마음에 드신분이 있다면, 몇번 애프터를 더 해보고 손이라도 잡아봐야 겠죠. 다음번엔 이어서 기본적인 타입에 대해서 좀 더 알아보고, .NET공원에서 놀이기구를 몇개 타보는 걸로 해볼 생각입니다. 그리고 이번포스트에서 제대로 조사하지 못한 부분역시 조사해볼 생각입니다. 데이트장소는 고전적이긴 하지만 놀이공원도 괜찮죠. 그럼 도움이 되는 분들이 있길 바라면서 다음 포스트에서 뵙겠습니다.

-참고자료
1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS.
2. http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec2.aspx#_Toc207785764
3. http://blogs.msdn.com/jaredpar/archive/2008/09/09/when-to-use-type-inference.aspx

Welcome to F#(1) - 첫만남.

F# 2009. 4. 5. 20:58 Posted by 알 수 없는 사용자

What is F#?
F#에 대해서 들어본 적이 있으신가요? 아니면, Functional programming language 혹은 함수형 언어라고는 들어본적이 있으신가요? 아마도 최근에 부각되었었던 Erlang이나 기존의 Lisp, Scheme등을 들어보셨거나, 공부해보신 분이라면 알고계시리라 생각합니다. F#은 닷넷에서 돌아가는 함수형언어이고 현재 CTP(Community Technical Preview)버전이 공개된 상태입니다. 차후에 VSTS 2010에 포함되어서 나올 예정이구요. 


함수형 언어는 왜 배워야 하는데?
C#이나 Java와 같이 입력을 받아서 어떻게 그걸 처리할지 주저리주저리 이야기 해줘야 하는 언어는 명령형 언어(Imperative programming language)라고 합니다. 하지만, 그와 반대로 어떻게 할건지 보다는 무엇을 처리할지에 더 관심을 두는 형태를 선언형 언어(Declarative programming language)라고 합니다. 정규식이나 함수형언어 같은 언어가 포함되지요. 예를 들어보면 정규식같은 경우는 어떤 패턴을 잡을지에 관심이 집중되어 있습니다. 하지만, 어떻게에 대해서는 관심이 없지요. 즉, 명령형 언어와 선언형 언어는 서로 관점이 틀리기 때문에 서로 유용하게 쓸 수 있는 분야도 틀릴 수 있다는 것입니다.

물론, Anders Hejlsberg가 JAOO의 강연에서 이야기 한것처럼 함수형언어와 기존의 명령형언어(Imperative programming language)는 서로 다른 장점이 있기 때문에, 어떤 상황에서 어떤 언어를 선택하는 것이 옳은지 명확하게 이야기 할 수 는 없습니다. 하지만, 함수형언어는 나중에 더 자세히 다룰 수 있겠지만 Immutable, 즉 한번 값을 입력하면 다시는 값이 변경될 수 없는(C#에서 readonly를 생각하면 되겠죠.)걸 이야기 하는데, 이때문에 병렬적으로 실행되는 환경에서 흔히 문제가 되는 여러 스레드가 하나의 값에 접근할때 값이 잘못변경될 위험이 없습니다. 그래서 병렬 프로그래밍에서 가장 유용한 프로그래밍 모델로 생각되고 있으며 실제로 얼마전에 Erlang이 이슈가 되었던 것도 그런이유 때문이었던 것 같습니다.

근데 왜 F#을 공부해야 하는데?
개인적으로는 점차 플랫폼의 영향력이 커진다는 느낌을 받습니다. 그리고 기존의 플랫폼에 워낙 많은 기능이 이미 구현되어 있고, 그런 언어의 실행환경에 대해서는 신경을 쓰지 않고, 자신의 언어가 가질 특징들에 집중해서 새로운 언어를 만드는 것도 점차 중요해지는 것 같구요. 꽤나 히트했던 Ruby가 JRuby와 IronRuby로 자바와 닷넷에 각각 편입되는 것을 보더라도 그런 것 같습니다. 단순히 플랫폼의 덕만 보는게 아니라, 그 플랫폼에서 사용되는 다른 언어들과도 교차적으로 사용될 수 있다는 점도 꽤나 큰 이익이 아닌가 생각합니다. 이 주제에 대해서 잘 설명을 해주신 장성진님의 글을 읽어보시면 더 좋을 것 같습니다.

F#을 배워야 하는 이유는 하나의 어플리케이션을 개발할때, C#을 메인언어로 놓고 개발하면서 F#으로 개발했을때 이익이 되는 부분이라면, F#으로 개발해서 조합을 할 수 있겠죠. 저 역시도 그런 가능성을 가장 기대하고 있습니다.

앞으로의 방향은 어떻게 되니?
개인적으로 기존에 Scheme을 공부해본적이 있긴하지만, 워낙 잠깐이라 딱히 많이 아는건 없습니다. 그래서 공부해가면서 고민해가면서 여기에 적어볼 생각입니다. 그래서 내용이 엄밀하지 못하거나 심도가 없을 수도 있는데, 그런부분은 피드백으로 잘 훈계해주시면 완전 감사하겠습니다.

F#을 설치해보자
시작이 반이라고 했으니, F#을 설치하면서 빨리 반을 끝내보도록 하죠. F#은 현재 CTP버전이 나와있고, 비주얼 스튜디오 2008에서도 사용할 수 있습니다. 그리고 비주얼 스튜디오가 없더라도 명령행툴이 있기 때문에, 거기서 작업을 하면서 공부하실 수 있습니다. 물론 비주얼 스튜디오가 있음 더 편하겠죠. 일단 여기에서 F# CTP버전을 받을 수 있습니다. msi파일을 받아서 실행하시면 되구요, zip파일은 윈도우가 아닌 운영체제에서 압축을 풀어서 readme를 보라고 하는데, 별로 보고싶지 않아서 확인은 안 해봤습니다.(역시 글로쓰면 농담이 안살아나는 군요-_-)

F#과 만나서 Hello를 외쳐보자
Hello를 외치려면 일단 만나야 겠죠. 그럼, F# Interative를 실행해보죠. 그러면 아래와 같은 창이 하나 뜹니다.


궁금한게 있으면 #help;;를 쳐보라는 친절한 말까지 남겨주시죠. 여기서 우리는 F# Interative에게 뭔가 부탁하려면 '# + 부탁할내용 + ;;'을 해야 한다는 사실을 알 수 있죠. 참고로 F# Interactive에서 나가게 해달라고 부탁하는 말은 "#quit;;"입니다. 그러면, 언제나 처음 언어를 접하면 서로 인사를 나누어야 하니 Hello World를 한번 외쳐보도록 하죠.



위와 같이 한번 쳐보시죠. let HelloWorld~~;;는 선언부이고, 그 아래는 함수호출 결과입니다. 언어가 다르다 보니 모양도 좀 틀립니다. 우선 F# Interative에서는 하나의 의미를 갖는 문장이 끝난다는걸 ';;'을 통해 알립니다. 그래서 위문장은 ';;'를 만날때 까지 하나의 단위로 인식해서 함수를 선언해주는 것이고, 아래 문장은 호출이라는 것을 파악하는 것이죠. 그러면 이제 나가게 해달라고 부탁하시고 비주얼 스튜디오에서도 Hello World를 외쳐보도록 하겠습니다.


새프로젝트를 눌러보시면 윗 그림과 같이 Visual F#이 추가된걸 보실 수 있습니다. 그럼 프로젝트를 생성해서 아래그림 처럼 코드를 쳐보시죠.



그리고 정말 친숙한 명령 Ctrl+F5를 누르면, 정말 친숙한 콘솔창에 아래 그림과 같이 결과가 나옵니다.(너무 친숙해서 마치 집에 있는거 같군요. 이래서 집나가면 개고생이라나 봅니다.)



그리고 비주얼스튜디오에서 F# Interative창을 띄워서 평가하고 싶을때는 아래 그림과 같이 "보기 -> 다른 창 -> F# Interactive"를 눌러줍니다.



그러면, 아래 그림과 같이 하단부에 F# Interactive가 뜨게됩니다.



그리고 이제 비주얼 스튜디오에서 작업한 내용을 F# Interactive에서 확인해볼 수 있습니다. F#같은 함수형 언어에서는 모든 코드가 컴파일되는 것이 아니고 한줄 한줄 평가(Evaluation, 혹은 그냥 Eval)하게 되는데요, 작업 부분중에 평가 해보고 싶은 부분만 드래그해서 F# Interative에서 결과를 확인해볼 수 있습니다. 평가하는 방법은 우선 평가하고 싶은 부분을 정확하게 드래그 해서 마우스 오른쪽 버튼을 누르고 "Send to F# Interative"를 누르거나 "Alt + Enter"를 누르시면 됩니다. 아래 그림은 함수선언부를 먼저 F# Interactive로 보내서 평가하고 그 다음에 함수호출부분을 다시 평가해서 얻은 결과물입니다.



이제는 우리가 헤어져야 할시간, 다음에 다시 만나요
자 이제, F#과의 첫 인사를 마쳤습니다. 어떠셨나요? 제가 실력이 부족하고 글솜씨가 딸려서 좀 설명이 부족한 부분이나, 틀린 부분이 있을 수도 있습니다. 그럴땐 따뜻한 피드백으로 화답해주셨으면 좋겠습니다.(차가운 피드백은 반사하겠습니다-_-) 저 역시 아직 함수형언어 자체에 대한 이해도 부족하고, F#도 잘 아는 상태가 아니라서 인지 앞으로 어떻게 계속 글을 이어나가야 겠다는 명확한 생각은 없는 상태고 그저 글을 계속 쓰면서 정해나갈 생각입니다. 다음번 포스트에서는 간단한 예제를 통해 F#에 대해서 좀 더 자세한 설명을 드리고 명령형언어와 함수형언어의 차이에 대해서 좀 더 알기 쉽게 설명해볼까 하는 생각을 갖고 있습니다.(말이 길어지면, 둘중 하나만 하고요-_-) 이 글이 도움이 되는 분들이 있길 바라고, 다음 포스트로 뵙겠습니다.

참고자료
1. Expert F#, Don Syme, Adam Granicz, Antonio Cisternino, APRESS.
2. http://blog.java2game.com/229
3. http://jaoo.blip.tv/file/1317881/
4. http://en.wikipedia.org/wiki/Functional_programming
5. http://en.wikipedia.org/wiki/Imperative_programming
6. http://en.wikipedia.org/wiki/Declarative_programming