금번 인수봉 등산 때 단체 사진 찍은 거
카카오톡 메시지로 공유드렸읍니다~
다음 지리산 등산 때는 꼭 사진기 거치대 챙겨가겠읍니다..!
이번 시간엔 OCaml이란 함수형 언어 공식도큐먼트에 나와있는
tutorial을 설명드리고자 글 작성합니다.
이 글은 공식 tutorial 90% + 제 생각/의견/도움말 10%로 이뤄져있습니다!
관심있는 분께 도움이 됐으면 좋겠읍니다.
또한, 이 글은 읽는 게 이 글의 효용의 전부가 아니라
댓글로 궁금한 사항을 물어보면 제가 답변해드린다는 것 또한 이 글의 가치라고 생각합니다.
Running OCaml Programs
OCaml Playground 환경에서 OCaml 처음 연습해보시고, 나중에 설치하셔도 좋을 거 같아요~
이건 개인의 취향이니까 패쑤~~~
혹시 설치가 어려우시면 댓글 남겨주세요~
Expressions and Variables
- Expression
# 50 * 50;;
- : int = 2500
위는 간단한 Expression(수식)입니다!
OCaml은 int, float, string, bool 등의 type을 갖고 있고, 매 Expression이 끝나면 세미콜론 두개!를 찍어줘야 합니다.
- Variable
# let x = 50;;
val x : int = 50
# x * x;;
- : int = 2500
위는 변수 x에 값 50을 넣습니다.
변수를 정의할 땐, let
키워드를 사용하여야 하며
따로 타입을 지정해주지 않아도 됩니다. (그 이유는 OCaml은 강력한 type 추론 능력이 있어서!)
let
키워드로만(in
키워드 없이) 변수를 정의할 경우, 글로벌 변수가 되어
다음줄에서 x에 접근할 수 있었습니다.
# let y = 50 in y * y;;
- : int = 2500
# y;;
Error: Unbound value y
다음은 let … in …
syntax입니다.
이는 변수의 범위(Scope)를 in 이후에 나오는 Expression로 제한합니다. 즉, y변수는 in 다음에 나오는 식 내에서만 존재하며,
밖에서 접근할 경우 Error가 납니다.
그리고 변수명은 소문자와 _(underbar)로 만들어야 합니다!
정리.
let
으로 변수 정의할 때의 범위는 global
let … in …
으로 변수를 정의할 때의 볌위는 in 이후에 나오는 식
그리고 추가적으로 OCaml에서는 변수는 immutable하기에, 한번 할당하고 나면, 그 값이 바뀔 수 수 없습니다.
# let a = 1 in
let b = 2 in
a + b;;
- : int = 3
위와 같이 let … in …
을 연속적으로 사용할 수 있습니다. == 하나의 Expression내에서 여러개의 변수를 만들 수 있습니다.
추가로, Expression의 단위는 ;;
을 기준으로 합니다.
Functions
let 키워드는 함수를 정의할 때도 사용할 수 있습니다.
(아래는 개인적인 생각)
이는 일급객체라는 개념과도 연관되는 데, OCaml은 함수를 변수에 할당될 수 있는 값으로 여깁니다.
즉, 함수는 값!
⇒ 값처럼 함수가 함수의 인자로 전달될 수 있고, 값처럼 함수가 함수의 리턴값이 될 수 있다!
OCaml의 함수 정의는 다음 포맷을 따릅니다.
let 함수명 [arg1, arg2, …] = Expression
# let square x = x * x;;
val square : int -> int = <fun>
# square 50;;
- : int = 2500
위는 간단한 함수의 예시입니다.
2번째 줄에서 알 수 있듯이 square라는 함수를 값으로 생각하였고, 그 type은 int → int
이며, int값을 받아 int값을 반환한다고 생각하여도 무방합니다.
함수를 호출할 땐, 함수명 + 인자
형식입니다.
# let ordered a b c =
a <= b && b <= c;;
val ordered : 'a -> 'a -> 'a -> bool = <fun>
# ordered 1 1 2;;
- : bool = true
위는 여러개의 함수 인자를 받는 함수의 예시입니다.
눈여겨 볼 점은 3번째 줄에 보면 이전과는 달리 함수 type이 ‘a라는 녀석으로 정의되습니다.
이는 a, b, c가 정수도 가능하고 실수도 가능하기에 추상적인 type으로서 ‘a을 쓴 것입니다.
또한 ‘a → ‘a → ‘a 으로 정의되었는데, 이는 각각의 argument에 대응됩니다.
‘a가 세번이나 쓰인 건 2번째 줄에서 알 수 있듯이 3 argument가 같은 타입이기 때문입니다.
만약 함수를 아래와 같이 정의한다면?!?!?!
# let function1 a b c d =
a <= b && c <= d;;
val function1 : 'a -> 'a -> 'b -> 'b -> bool = <fun>
OCaml은 타입에 있어서 매우 빡빡하게 굴기에 implicit type casting을 하지 않습니다.
즉, C에서 처럼 int + float를 float를 계산하는 서비스?가 제공되지 않습니다.
따라서 +
는 두 int 사이의 덧셈에 사용되고, +.
는 두 float 사이의 덧셈에 사용됩니다.
만약, 정수를 실수로 바꾸고 싶으면 명시적으로 float_of_int 함수를 사용해야합니다.
Recursive Functions
OCaml에서는 rec
키워드로 함수를 정의하지 않은 경우, 함수내에서 자기 자신을 호출하지 못합니다.
즉, 재귀함수는 let rec 함수명 [arg1, arg2, ,,,] = Expression
형식으로 정의합니다.
다음은 팩토리얼을 계산하는 Recursive function입니다.(제가 추가한 내용)
let rec factorial n =
if n == 0 then 1
else n * factorial (n - 1)
;;
factorial 5;;
OCaml에서 조건문은
if 조건 then 값
else if 조건 then 값
else 값
의 형태를 갖습니다.
Types
OCaml의 타입에서는 할 얘기가 많지만 간결하게
다음과 같이 정리하겠습니다.
- basic types : int, float, bool, char, string
- OCaml은 매우 강력하게 type에 깐깐함 ⇒ 모든 Expression은 오직 한개의 type을 가질 수 있다
- OCaml은 매우 강력한 타입 추론기를 내장함 ⇒ 굳이 type을 명시하지 않아도, 프로그램 시작 전에 싹다 추론해버림
- OCaml은 implicit type casting을 하지 않음 ⇒ 2.0 + 2는 에러!!!!!
Pattern Matching
switch문과 비슷함
형태
match 대상변수 with
| pattern1 | pattern2 | ... -> 값1
| pattern1' | pattern2 | ... -> 값2
...
| _ -> 값n
위와 같이 pattern을 |
을 사용해서 열거 할 수 있고 →
이후에 해당 pattern일 때의 값을 적는다.
else는 보통 _
를 사용함
이를 이용하여, factorial 재귀 함수 정의
let rec factorial n =
match n with
| 0 -> 1
| _ -> n * factorial (n - 1)
;;
factorial 3;;
List
리스트 생성
OCaml에서는 []
로 빈 리스트를 만들고,
[인자; 인자; 인자; … ]
초기 인자와 함께 리스트를 만들 땐 인자를 ;
으로 구분합니다.
# [];;
- : 'a list = []
# [1; 2; 3];;
- : int list = [1; 2; 3]
# [false; false; true];;
- : bool list = [false; false; true]
# [[1; 2]; [3; 4]; [5; 6]];;
- : int list list = [[1; 2]; [3; 4]; [5; 6]]
리스트 관련 메소드
::
:값 :: 리스트
⇒ 리스트 맨 앞에 값 삽입
@
:리스트 @ 리스트
⇒ 리스트 합치기
- List.hd/tl :
List.hd [1;2;3]
⇒ 리스트의 head혹은 tail 값 반환
# let rec total l =
match l with
| [] -> 0
| h :: t -> h + total t;;
val total : int list -> int = <fun>
# total [1; 3; 5; 3; 1];;
- : int = 13
위 예제는 정수 리스트를 받아, 인자들의 총합을 계산하는 함수로서
리스트가 empty일 때를 처리하는 로직(3번째 줄) 필요
아래는 list와 관련된 사용자 정의 메소드들
한번 직접 이해해봅시다!
# let rec length l =
match l with
| [] -> 0
| _ :: t -> 1 + length t;;
val length : 'a list -> int = <fun>
# let rec append a b =
match a with
| [] -> b
| h :: t -> h :: append t b;;
val append : 'a list -> 'a list -> 'a list = <fun>
# let rec map f l =
match l with
| [] -> []
| h :: t -> f h :: map f t;;
val map : ('a -> 'b) -> 'a list -> 'b list = <fun>
리스트 관련 메소드를 OCaml의 Standard Library를 이용하여 사용 가능합니다.
Other Built-In Types
Tuple
OCaml은 tuple 타입을 제공합니다.
# let t = (1, "one", '1');;
val t : int * string * char = (1, "one", '1')
위 tuple의 타입을 보면 int * string * char로 정의됨
리스트와 달리 각각의 값들이 ,
로 구분됩니다.
Records
tuple과 비슷한 Records는, elements의 순서가 정해져있고, 각각의 element의 이름이 정해져있습니다.
마치 python의 Dictionary와 비슷하다고 할 수 있겠습니다.
# type person =
{first_name : string;
surname : string;
age : int};;
type person = { first_name : string; surname : string; age : int; }
# let frank =
{first_name = "Frank";
surname = "Smith";
age = 40};;
val frank : person = {first_name = "Frank"; surname = "Smith"; age = 40}
# let s = frank.surname;;
val s : string = "Smith"
Our Own Data Types
OCaml의 사용자 정의 타입!
타 언어의 enum과 비슷한 느낌이?
사용자 정의 타입은
type키워드 + 타입명 = 값1 | 값2 | 값3 | …;;
형식으로 정의돼야 하며, 값은 아래와 같이 대문자로 시작해야 합니다.
# type colour = Red | Blue | Green | Yellow;;
type colour = Red | Blue | Green | Yellow
# let l = [Red; Blue; Red];;
val l : colour list = [Red; Blue; Red]
그리고 값에 더불어 추가적인 값들을 함께 사용할 수 있습니다. (아래의 RGB)
# type colour =
| Red
| Blue
| Green
| Yellow
| RGB of int * int * int;;
type colour = Red | Blue | Green | Yellow | RGB of int * int * int
# let l = [Red; Blue; RGB (30, 255, 154)];;
val l : colour list = [Red; Blue; RGB (30, 255, 154)]
추가적인 값들은 아래와 같이 match문으로 접근할 수 있습니다.
type colors =
| Red
| Green
| Blue
| RGB of int * int * int
;;
let rec getColor c =
match c with
| Red -> RGB (255, 0, 0)
| Green -> RGB (0, 255, 0)
| Blue -> RGB (0, 0, 255)
| RGB (r,g,b) -> RGB (r,g,b)
;;
getColor Red;;
그리고 사용자 정의 타입 내부에서 recursive한 정의와 polymorphic한 정의 또한 가능합니다.
여기서 polymorphic이란, java의 Generic과 비슷한 개념입니다.
따라서 아래와 같이 tree앞에 ‘a라는 polymorphic을 사용함으로써,
int에 대한 tree와 char에 대한 tree를 한번에 정의 가능
type 'a tree =
| Leaf
| Node of 'a tree * 'a * 'a tree;;
let tree1 = Node (Node(Leaf, 1, Leaf), 2, Node (Node(Leaf, 3, Leaf), 4, Leaf));;
let tree1 = Node (Node(Leaf, 'a', Leaf), 'b', Node (Node(Leaf, 'c', Leaf), 'd', Leaf));;
추가로, OCaml은 Garbage-collector가 돌아가서, 명시적인 free가 필요없어요!!!
이 다음 내용은 갑자기 집중력 이슈로 추후에 …
당장 지금할 건 위의 내용들로 커버가 될 듯합니다 …ㅎㅎㅎ
추가 사항 (오지랖)
# let ordered a b c =
a <= b && b <= c;;
val ordered : 'a -> 'a -> 'a -> bool = <fun>
위와 같이 왜 함수의 인자는 ‘a * ‘a * ‘a → bool
과 같은 tuple의 형태가 아니라
‘a → ‘a → ‘a → bool
의 형태일까?
그 답은!!!!!
아래를 보면 알 수 있다!!!
let sum a b =
a + b
;;
let incr = sum 1;;
incr 10;
즉, let sum (x, y) = x + y;;
는 tuple 한개를 받는 것이고,
let sum x y = x + y;;
는 두 인자를 차례대로 받는 것(이를 Currying이라고 하는 듯)
즉22, let sum x y = x + y;;
는 사실 int 하나를 받아서 int → int 타입 함수를 내놓는 거라 생각해도 무방
추가22 (잔소리)
- 여기서 자세히 소개는 안했지만, OCaml을 잘하고 싶다면 Polymorphic Type을 이해하는 게 중요할 듯!!
- 함수도 값이다!!!
- OCaml은 main 함수가 없이 맨 위에서부터 실행됩니당~
- 기존에 많은 분들이 C언어와 같은 절차형 프로그래밍을 많이해서,
함수형 프로그래밍의 개념이 생소할 수 있는데
절차형 프로그래밍은 컴퓨터에게 순서대로 명령을 시키는 느낌이고
함수형 프로그래밍은 순차적으로 값을 굴려나가는 느낌으로, 컴퓨터에게 값의 계산을 시키는, 값 중심의 언어입니다.
이를 이론적으로 이해하고 체감하는 건 어려운 일이라 여러 예제를 코딩해보시면 그 감을 잡을 수 있습니다.이 친구가 하다보면 굉장히 맛깔납니다!
함수형 프로그래밍을 잘하면 회사에서 이쁨 받아요!
자료
회원님들 오늘 글은 여기까지 입니다~
다음 가을맞이 등산 때 뵙겠습니다.
추신.
이 추신을 읽은 사람 당신. 댓글 띱!