KoreanFoodie's Study
OCaml 기초 2 - 자료형, Currying, match-with, polymopic type, reference 본문
OCaml 기초 2 - 자료형, Currying, match-with, polymopic type, reference
GoldGiver 2019. 9. 17. 17:21
이전 게시글에 이어 OCaml에서 꼭 알아야 할 중요한 개념들에 대해 더 알아보도록 하자.
주요 개념들 : Pair, Tuple, List, Currying, Inductive type, match-with 구문, Polymorphic type, try-with-raise, module system, reference.
OCaml Pair
Pair는 두 개의 값을 한번에 묶는 데 활용된다. 정의는 (x, y)
같은 식으로 할 수 있으며, 타입은 a * b
형태로 표기된다.
Pair의 각 항을 조회하는데 fst
와 snd
함수를 사용할 수 있다.
let p:(int * string) = (1, "a")
let i = fst p (* int, 1 *)
let s = snd p (* string, "a" *)
let (i, s) = p (* 이렇게 해서 i 와 s값을 정의해줄 수도 있다.)
OCaml Tuple
Tuple은 Pair의 연장선이라고 볼 수 있다. Pair가 2개의 값을 묶을 수 있는 반면, Tuple은 그 이상의 값들을 한번에 묶을 수 있다.
대신, Pair의 fst, snd와 같은 함수는 사용할 수 없다.
let t:(int * string * float) = (1, "a", 1.5)
let (i, s, f) = t (* f가 1.5값이 된다 *)
let (_, _, f) = t (* 윗줄과 같음. _ 은 type과 값에 관계없이 자리를 채워준다 *)
OCaml List
List는 OCaml을 사용한다면 정말 많이 사용하게 되는 자료구조이다.
리스트는 빈 리스트 ([ ]
)이거나 원소와 리스트의 결합 (x::y
)로 표현된다. 이때, x가 int 타입이면, y는 int list 타입이다.
리스트는 두 가지 방식으로 정의할 수 있다.
let x: int list = 1 :; 2:: 3:: []
let x: int list = [1; 2; 3]
- List 다루기
리스트는 head와 tail로 나뉘게 된다. head는 리스트의 첫 항을 의미하며, tail은 head를 제외한 나머지 리스트를 의미한다.
예제를 통해 어떻게 동작하는지 자세히 알아보자.
let head_elem: int = List.hd [1; 2; 3] (* 1 *)
let tail_list: int list = List.tl [1; 2; 3] (* [2 ; 3] *)
let elem: int = List.nth [1; 2; 3] 1 (* 2. 두번째 원소를 리턴한다 *)
if (List.mem 1 [1; 2; 3]) then ... else ...
List의 원소는 모두 같은 타입이어야 한다.
단, tuple은 다른 타입의 여러 값들도 묶을 수 있다.
OCaml Currying
Currying은 기존 C, C++, Java, Python등에서 보기 힘든 함수형 언어(Scala 등)의 특색이라고 할 수 있다.
함수가 여러 개의 인자를 받아야 할 때, pair로 묶어서 한번에 받는다면 이런 식으로 쓸 수 있다.
let x (a, b) = a + b (* (int * int) -> int *)
하지만 currying을 사용하게 되면, 두 인자를 차례대로 받을 수 있다.
let y a b = a + b (* int -> int -> int *)
두 함수의 타입은 다르다! (중요)
그럼 두 번째 예시의 currying 타입을 이해해 보자.
y 함수는 int를 두 번 받아서 int를 결과값으로 내놓는 함수이다.
조금 더 살펴보면, int를 받아서 int -> int
타입 함수를 내놓는 함수이다.
즉, a b
두 개의 인자에서 a
만 인자로 준다면, y 함수는 a + (앞으로 줄 인자)
를 수행해 int를 뱉어내는 함수가 된다!
즉, 다음과 같이 함수를 쪼갤 수도 있는 것이다.
let incr = sum 1 (* incr: int -> int *)
OCaml - Inductive type
사용자가 직접 타입을 정의할 수도 있다. 이 타입 정의는 inductive한 형태도 가능하다.
예를 들어, 정수 이진 트리 타입을 정의한다고 생각해 보자.
type tree = Leaf of int
| Node of int * tree * tree
let t1: tree = Leaf 1
let t2: tree = Node(1, Leaf 2, Leaf 3)
이때, 사용자 정의 타입의 constructor (Leaf, Node 등)는 대문자로 시작해야 한다.
match-with 구문
패턴 매칭은 케이스를 나누어 계산하는 편리한 문법 요소이다.
match-with 구문을 이용하여, 앞에서 정의한 정수 이진 트리 원소의 합을 구해보자.
type tree = Leaf of int
| Node of int * tree * tree
let rec sum_of_tree: tree -> int = fun mytree ->
match mytree with
| Leaf i -> i
| Node (i, l, r) ->
i + (sum_of_tree l) + (sum_of_tree r)
이런 식으로 코드를 짜면, recursive하게 sum_of_tree
함수가 Leaf가 나올 때가지의 값을 알아서 계산해 준다.
주의사항
match-with의 경우, 중첩이 가능하다. (match-with 아래에 match-with 사용 가능)
이때, 괄호를 잘 붙여 주어야 한다.
let foo x =
match x with
|A -> ...
|B -> ...
|C y ->
(match y with
|K -> ...
|L -> ... )
|D -> ...
OCaml Polymorphic type
Polymorhphic type을 활용하면, 함수가 인자를 받거나 할 때, 굳이 타입을 명시하지 않더라도 여러 타입을 받아 각 타입에 맞게 함수가 동작하도록 만들 수 있다.
예를 들어, 정수 리스트의 길이를 구하는 함수와, 문자열 리스트의 길이를 구하는 함수를 짠다고 가정하자.
let len = List.length [1; 2; 3] (* 3 *)
let len = List.length ["a"; "b"; "c"; "d"] (* 4 *)
굳이 타입에 맞춰 함수를 새로 구성할 필요없이, refactoring이 가능하다.
이는 List가 다형(Polymorphic) 타입이라서 가능하다.
type 'a list = [ ] | 'a ::'a list
(* Pseudocode, does not compile *)
(* 'a list 가 빈 리스트([ ]) 이거나, 'a 타입에 'a 타입 리스트를 붙인 리스트 형태라는 것을 의미한다 *)
이때, 'a
는 임의의 type을 의미한다.
'a
가 int면 정수 리스트, 'a
가 string이면 문자열 리스트가 된다.
그러므로, list의 길이를 구하는 함수는 다음과 같이 한 번만 정의하고 써 주면 된다.
let rec list_len: 'a list -> int = fun mylist ->
| [] -> 0
| head::tail -> 1 + list_len tail
실제 OCaml 의 List 라이브러리도 비슷하게 ( = 다형 타입 덕분에 효율적으로) 구현되어 있다.
OCaml try-with(raise) 구문
다음으로는 예외처리에 대해 알아보자.
이는 Java/C++ 등의 예외 처리와 비슷하다.
try로 예외가 생길만한 부분을 묶고, with로 예외를 처리하며, raise는 예외를 발생시킨다.
let do_div: int -> int -> unit = fun x y ->
try print_endline (string_of_int (x/y)) with
Division_by_zero -> print_endline "Div by 0"
let f x =
if x < 0 then
raise (Failure "invalid input")
else x
Ocaml module
OCaml의 모듈은 관련 있는 코드들의 묶음이다. 파이썬을 써 본 사람은 매우 익숙할 것이다.
Signature는 모듈에서 드러내고 싶은 것만 드러낸다는 뜻이고, Functor는 모듈을 받아서 모듈을 내놓는 것을 의미한다.
모듈은 내용이 많으므로, 차후 다루도록 하겠다.
OCaml reference
OCaml은 값 중심 언어이지만, 명령형(imperative) 스타일로 쓰는 요소도 지원한다. 이는 C에서의 포인터와 비슷하다.
변수를 선언하고 값을 저장하는 스타일을 보자.
- 0을 가리키는 참조자(reference) 정의
let int_ref = ref 0
값을 가져오려면 !
연산자를 사용한다.
let value_of_ref: int = !int_ref
새 값을 저장할 때에는 :=
연산자를 사용한다.
let _ = int_ref := !int_ref + 1 (* "i++" in C *)
추후에 더 많은 예제를 다뤄보도록 하자.
'Tutorials > Ocaml' 카테고리의 다른 글
OCaml Library module "List" implementation (0) | 2019.09.17 |
---|---|
OCaml 예제 코드 (Quicksort, Stack, Calculator, Prime Number Generator) (0) | 2019.09.17 |
OCaml 기초 - OCaml 타입, 함수 (0) | 2019.09.16 |