OCaml은 모듈 시스템을 지원한다.
OCaml 프로그램은 여러 개의 모듈로 구성되어 있으며, 각각의 모듈은 함수와 변수로 이루어져 있다.
모듈은 클래스와는 다르게, 그 자체로 type은 아니다.
모듈을 객체화하는 것은 불가하며, 모듈의 함수를 호출하거나 자료를 읽는 행위만 가능하다.
OCaml에서도 클래스를 지원하긴 하지만, 잘 사용되지 않는다.
모든 소스 코드 파일은 그 자체로 모듈이다.
예를 들어, source.ml
이라는 소스 코드 파일을 작성하였다면,
해당 소스 파일을 Source
이라는 이름의 모듈로 사용할 수 있다.
모듈 내부의 변수 또는 함수에 접근하기 위해서는 .
기호를 사용하며,
사용 방법은 [module_name].[var_name]
이다.
example)
Format.printf "Hello!"; (* Format 모듈의 printf 함수를 호출 *)
Nested Module이란, 모듈 내부에 정의된 모듈을 의미한다.
생성 방법 : module [module_name] = struct [defs] end
[module_name]
: Nested Module의 이름으로, 반드시 대문자로 시작해야 한다.
[defs]
: definitions의 나열
example - integerOperators.ml)
module IntOP = struct
let add x y = x + y
end
Nested Module에 접근하기 위해서는 상위 모듈로부터 단계별로 접근한다.
접근 방법 : [module_name].[nested_module_name].[var_name]
example)
Format.printf "3 + 7 = %d\n" (IntegerOperators.IntOP.add 3 7);
(* integerOperator.ml의 nested module인 IntOP의 내부 함수 add를 호출 *)
모듈을 open하여 모듈의 변수, 함수에 접근할 때 모듈 이름을 생략할 수 있다.
사용법 : open [module_name]
example)
open IntegerOperators
(* 모듈 IntegerOperators Open, 해당 모듈 내부의 변수 및 함수에 모듈 이름 없이 접근 가능 *)
let _ =
Format.printf "3 + 7 = %d\n" (IntOP.add 3 7);
let-in expression을 사용하여 특정 scope에서만 모듈을 open할 수도 있다.
사용법 : let open [module_name] in [expression]
expression에서의 계산은 모듈이 open된 상태로 진행된다.
example)
let open IntegerOperators.IntOP in
let_ = Format.printf "3 + 7 = %d\n" (add 3 7);
👻 Module Open 시 주의할 점 👻
여러 모듈에 같은 이름의 함수가 있을 때에는 충돌이 발생할 수 있으므로 주의해야 한다.
모듈 이름이 긴 경우, 모듈 이름을 줄일 수 있다.
모듈 이름 변경 방법 : module [abbreviation] = [module_name]
[abbreviation]
: module 이름을 지칭할 약자
example)
module IOP = IntegerOperators.IntOP
let_ = Format.printf "3 + 7 = %d\n" (IOP.add 3 7);
Pattern Matching은 값의 형태에 따라 다른 동작을 수행하는 기능으로,
값이 특정한 패턴에 부합하면 해당 패턴에 매칭되는 동작을 수행한다.
C/C++의 switch-case와 유사하나 더 강력한 표현력을 제공한다.
사용법 :
match [expression] with
| [pattern1] -> [expression1]
| [pattern2] -> [expression2]
...
| [patternN] -> [expressionN]
먼저 [expression]
을 계산한 뒤, 계산 결과를 [pattern1]
과 비교한다.
매칭되는 경우, [expression1]
의 계산 결과가 전체 match-with expression의 결과 값이 된다.
매칭되지 않은 경우, [pattern2]
와 비교하며 반복한다.
패턴은 상수일 때와 변수일 때 다르게 동작하는데,
패턴이 상수인 경우, 위에서 정리한 것과 같이 계산 값이 패턴과 일치하는지 확인한다.
그러나 패턴이 변수인 경우, 계산 값을 변수에 바인딩하고 항상 매칭에 성공한다.
만약 변수 패턴이 등장한 뒤 다른 패턴이 등장한다면, 변수 패턴 뒤의 패턴들은 무시된다.
패턴으로 _(wildcard)
가 등장할 수도 있는데, 이는 변수 패턴과 마찬가지로 anything을 의미한다.
example)
let x = 10 in
match x with
(* 패턴이 일치하는지 확인 -> 일치하지 않으므로 다음 패턴으로 이동 *)
| 0 -> "zero"
(* 변수 패턴 확인, 변수 a에 expression x의 결과 값 10을 바인딩 *)
(* 변수 a를 사용하지 않았으므로 Warning 26이 발생 *)
| a -> "not zero"
(* 무시됨 *)
| 3 -> "three?"
(* 무시됨 *)
| b -> "not zero?"
let y = 20 in
match x with
(* 패턴이 일치하는지 확인 -> 일치하지 않으므로 다음 패턴으로 이동 *)
| 0 -> "zero"
(* 변수 패턴 확인, match-with expression의 결과 값으로 "not zero"가 바인딩 *)
| _ -> "not zero"
(* 무시됨 *)
| 9 -> "nine?"
OCaml에서 pattern matching에서는 모든 경우의 수를 커버해야 한다.
OCaml 컴파일러가 expression의 타입을 기반으로 패턴 매칭의 완전성을 검사하기 때문이다.
만약 모든 경우를 커버하지 못한다면 컴파일 Warning이 발생한다.
example)
let x : int = 10 in (* x의 type은 int *)
match x with
| 0 -> "zero"
| _ -> "not zero"
let y : int = 0 in (* y의 type은 int *)
match x with
| "0" -> "zero" (* string type의 pattern 등장 : Match_failure *)
| _ -> "not zero"
변수 pattern / wildcard pattern
변수 pattern이나 wildcard pattern이 등장하면,
type 불일치로 인한 Match_failure 예외가 발생하지 않는 한 항상 매칭된다.
해당 패턴 이후로 오는 모든 패턴은 무시된다.
..
기호를 사용한 연속된 문자 혹은 숫자에 대한 범위 pattern
example)
'a' .. 'z' : a부터 z 까지의 모든 알파벳 문자
'c' .. 'j' : c부터 j까지의 모든 알파벳 문자
1 .. 10 : 1부터 10까지의 모든 정수
튜플이나 여러 개의 원소로 이루어진 expression이 등장하면,
해당 expression의 양식과 맞추어 pattern을 작성할 수 있다.
example)
let get_fst (p : int * int) : int =
match p with
| (fst, _) -> fst
let get_zero (x : int) (y : int) : string =
match x, y with
| 0, _ -> "front"
| _, 0 -> "back"
| _, _ -> "no zero"
[]
: 빈 리스트인 경우hd::tl
: hd는 리스트의 첫 번째 원소, tl은 나머지 리스트응용하여, 다음과 같은 패턴도 만들 수 있다.
hd::sec::tl
: hd는 리스트의 첫 번째 원소, sec은 두 번째 원소, tl은 나머지 리스트
OCaml에서 list는 다른 프로그래밍 언어들과 같이 여러 개의 값을 순차적으로 저장하는 자료구조이다.
튜플은 원소의 타입이 서로 다를 수 있었지만, 리스트의 경우에는 모든 원소의 타입이 동일해야 한다.
리스트의 타입 표기 : [type] list
e.g) int list, string list, char list, .. etc
리스트의 생성 : 리스트는 대괄호([]
)를 사용해 생성하며, 각 원소는 세미콜론(;
)을 통해 구분한다.
e.g) [] : 'a list, [1;2;3] : int list, ["e1";"e2";"e3";"e4"] : string list
리스트 연산자
::
: 리스트의 앞에 원소를 삽입
@
: 리스트를 결합
example)
let lst = [1] in (* lst = [1] *)
let lst' = 0 :: lst in (* lst' = [0;1] *)
let lst'' = lst' @ [2;3;4] in (* lst'' = [0;1;2;3;4] *)
OCaml은 list에 대한 내장 라이브러리를 지원한다.
대표 함수 :
List.iter
: 함수와 리스트를 인자로 받아, 리스트의 각 원소에 함수를 적용
List.map
: 함수와 리스트를 인자로 받아, 리스트의 각 원소에 함수를 적용한 새로운 리스트를 반환
List.rev
: 리스트를 인자로 받아, 반전된 리스트를 반환
OCaml은 사용자가 새로운 타입을 정의하는 것을 허용한다.
정의 방법 : type [type_name] = [type]
e.g) type int_list = int list
[type_name]
은 소문자여야 한다.
여러 types를 묶어 하나의 식별자로 나타낼 수도 있다.
type [type_name] =
| [name1] (of [type1])
| [name2] (of [type2])
| [name3] (of [type3])
...
| [nameN] (of [typeN])
of [type]
은 생략 가능하며, [name]
은 반드시 대문자로 시작해야 한다.
example)
type number =
| Zero
| Integer of int
| Real of float
Pattern matching에서, Disjoint union 값에 대해 Constructor을 사용하여 case를 분석할 수 있다.
example)
type number =
| Zero
| Integer of int
| Float of float
let print (n : number) =
match n with
| Zero -> F.printf "Val: zero\n" (* Zero에 연결된 값이 없음 *)
| Integer i -> F.printf "Val: Int %d\n" i
| Float f -> F.printf "Val: Float %f\n" f
let _ =
let _ = print Zero in (* Val: Zero *)
let _ = print (Integer 1) in (* Val: Int 1 *)
print (Float 9.9) (* Val: Float 9.9 *)