UPL Ch.2

chezze·2023년 4월 19일
0
post-thumbnail

Module System of OCaml

OCaml은 모듈 시스템을 지원한다.
OCaml 프로그램은 여러 개의 모듈로 구성되어 있으며, 각각의 모듈은 함수와 변수로 이루어져 있다.

Module vs Class

모듈은 클래스와는 다르게, 그 자체로 type은 아니다.
모듈을 객체화하는 것은 불가하며, 모듈의 함수를 호출하거나 자료를 읽는 행위만 가능하다.
OCaml에서도 클래스를 지원하긴 하지만, 잘 사용되지 않는다.

About Module

모든 소스 코드 파일은 그 자체로 모듈이다.
예를 들어, source.ml 이라는 소스 코드 파일을 작성하였다면,
해당 소스 파일을 Source 이라는 이름의 모듈로 사용할 수 있다.

모듈 내부의 변수 또는 함수에 접근하기 위해서는 . 기호를 사용하며,
사용 방법은 [module_name].[var_name] 이다.

example)
Format.printf "Hello!";		(* Format 모듈의 printf 함수를 호출 *)

Nested Module

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 Module

모듈을 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 시 주의할 점 👻
여러 모듈에 같은 이름의 함수가 있을 때에는 충돌이 발생할 수 있으므로 주의해야 한다.

Rename Module

모듈 이름이 긴 경우, 모듈 이름을 줄일 수 있다.

모듈 이름 변경 방법 : module [abbreviation] = [module_name]

[abbreviation] : module 이름을 지칭할 약자

example)
module IOP = IntegerOperators.IntOP

let_ = Format.printf "3 + 7 = %d\n" (IOP.add 3 7);

Pattern Matching

Pattern Matching은 값의 형태에 따라 다른 동작을 수행하는 기능으로,
값이 특정한 패턴에 부합하면 해당 패턴에 매칭되는 동작을 수행한다.

C/C++의 switch-case와 유사하나 더 강력한 표현력을 제공한다.

How to use pattern matching in OCaml?

사용법 :

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이 발생한다.

Categorization of patterns

  1. match - with 구분의 expression과 같은 type의 value
    만약 다른 type의 value가 패턴으로 주어진다면, Match_failure 예외가 발생하게 된다.
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"
  1. 변수 pattern / wildcard pattern
    변수 pattern이나 wildcard pattern이 등장하면,
    type 불일치로 인한 Match_failure 예외가 발생하지 않는 한 항상 매칭된다.
    해당 패턴 이후로 오는 모든 패턴은 무시된다.

  2. .. 기호를 사용한 연속된 문자 혹은 숫자에 대한 범위 pattern

example)
'a' .. 'z' : a부터 z 까지의 모든 알파벳 문자
'c' .. 'j' : c부터 j까지의 모든 알파벳 문자
1 .. 10 : 1부터 10까지의 모든 정수
  1. 튜플이나 여러 개의 원소로 이루어진 pattern

튜플이나 여러 개의 원소로 이루어진 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"
  1. List pattern
    Ocaml에서 List에 대한 패턴은 크게 다음 2가지로 나눌 수 있다.
    [] : 빈 리스트인 경우
    hd::tl : hd는 리스트의 첫 번째 원소, tl은 나머지 리스트

응용하여, 다음과 같은 패턴도 만들 수 있다.
hd::sec::tl : hd는 리스트의 첫 번째 원소, sec은 두 번째 원소, tl은 나머지 리스트

List

OCaml에서 list는 다른 프로그래밍 언어들과 같이 여러 개의 값을 순차적으로 저장하는 자료구조이다.

Tuple vs List

튜플은 원소의 타입이 서로 다를 수 있었지만, 리스트의 경우에는 모든 원소의 타입이 동일해야 한다.

List in OCaml

리스트의 타입 표기 : [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 : 리스트를 인자로 받아, 반전된 리스트를 반환

Type definition

OCaml은 사용자가 새로운 타입을 정의하는 것을 허용한다.

How to define new type

정의 방법 : type [type_name] = [type]
e.g) type int_list = int list

[type_name]소문자여야 한다.

Disjoint union

여러 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 *)
profile
주니어 컴공학부생🌱

0개의 댓글