[PL] Ocaml : 모듈 시스템, 패턴 매칭

parkheeddong·2023년 5월 30일
0
post-thumbnail



1. 모듈 시스템

Ocaml 프로그램은 "모듈"로 구성된다.

모듈은 자바에서의 'class' 개념과 유사하다.
각 모듈은 데이터(변수)와 함수들로 구성된다.

📌 모듈과 클래스의 차이

모듈은 type이 아니기 때문에, 모듈은 인스턴스화(Instantiate)할 수 없다.




1) ocaml에서 모든 소스 파일은 모듈이다.

// main.ml
let _ =
  let _ = Format.printf "Result: %d\n" Operation.def_val in
    Format.printf "Result %d\n" (Operation.add 3 7)

// operation.ml
let def_val = 99
let add x y = x + y

위 예시에서 main, operation 두 파일 모두 모듈이다.

🌱 모듈에 정의된 변수와 함수에 대한 접근

모듈에 정의된 변수나 함수는 [module_name].[var_name]으로 접근할 수 있다.
이렇게 접근하기 위해서 모듈의 이름은 대문자가 된다.
operation.ml 파일이면, Operation.add 로 참조해야 한다.




2) Nested Module

Ocaml에서는 Nested Module을 정의할 수 있다.

자바, C++에서의 inner class 개념과 유사하다.

🌱 Nested Module 정의

module [module_name] = struct [defs] end 이다.
module_name은 대문자로 시작해야 한다.

🌱 Nested Module 접근법

[module_name].[nested_module_name].[var_name]이다.

// main.ml

let _ =
  let _ = Format.printf "Result: %d\n" Operation.IntOp.add in
    Format.printf "Result %d\n" (Operation.FloatOp.add 3.0 7.0)

// operation.ml

module IntOp = struct
  let add x y = x + y
end

module FloatOp = struct
  let add x y = x + y
end



3) open keyword

open keyword를 사용해서 module의 이름을 생략할 수 있다.

open [module_name]으로 작성하며, 모듈의 definition을 import한다.

// Operation을 omit
open Operation
let _ =
  let _ = Format.printf "Result: %d\n" IntOp.add in
    Format.printf "Result %d\n" (FloatOp.add 3.0 7.0)


// Operation, IntOp omit
open Operation
open IntOp
let _ =
  let _ = Format.printf "Result: %d\n" add in
    Format.printf "Result %d\n" (FloatOp.add 3.0 7.0)

// operation.ml 

module IntOp = struct
  let add x y = x + y
end

module FloatOp = struct
  let add x y = x + y
end 

❌ Conflict가 발생할 수 있다.

open Operation
open IntOp
open FloatOp
let _ =
  let _ = Format.printf "Result: %d\n" add in // FloatOp.add
    Format.printf "Result %d\n" (add 3.0 7.0) // FloatOp.add

이 경우, add 함수가 IntOp와 FloatOp 모두에 정의되어 있기 때문에 이 경우 conflict가 발생한다.
이 때 마지막으로 Open된 FloatOp의 함수 FloatOp.add가 불러진다.
그러나 컴파일러는 FloatOp.add 에 integer 값인 3 과 7을 전달하였기 때문에 에러를 발생시킬 것이다 !




4) 축약형 표현(Abbreviation)

module [abbreviation] = [module_name]으로, 축약형 표현으로 Module을 참조할 수 있다.

Module OI = Operation.IntOp
Module F = Format

let _ = F.printf "Result %d\n" (OI.add 3 7) in
let _ = F.printf "Result %d\n" (OI.add 1 4) in
let _ = F.printf "Result %d\n" (OI.add 4 12) in
F.printf "Result: %d\n" (OI.add 2.5)



5) 모듈 범위 지정

모듈을 specific scope에서 open할 수도 있다.
let open [module_name] in [expression]
이전의 경우는 global scope에서 open한 것이라면, 아래 예시는 specific scope에서 module을 open한 것이다.

let _ =
  let open Operation.IntOp in
  let _ = F.printf "Result %d\n" (OI.add 3 7) in
  let _ = F.printf "Result %d\n" (OI.add 1 4) in
  let _ = F.printf "Result %d\n" (OI.add 4 12) in
  F.printf "Result: %d\n" (OI.add 2.5)



2. 패턴 매칭

Ocaml은 강력한 패턴 매칭 문법을 제공한다.

자바의 switch 와 비슷하지만, 더욱 더 강력한 도구이다.




1) Match-with 문법

match expression with
| pattern1 -> expression1
| pattern2 -> expression2
...
| patternN -> expressionN



🔔 Example : Fib 함수

module F = Format

module Fib = struct
  let rec fib i =
    match i with
    | 0 -> 0
    | 1 -> 1
    | n -> fib (n-2) + fib(n-1)
end

let _ =  
  let _ = F.printf "Res: %d\n" (Fib.fib 0) in
  let _ = F.printf "Res: %d\n" (Fib.fib 1) in
  let _ = F.printf "Res: %d\n" (Fib.fib 2) in
  F.printf "Res: %d\n" (Fib.fib 3)

🔔 Example : Even_or_ODD 함수

let _ =
  let even_or_odd i = 
    match i mod 2 with
    | 0 -> F.printf "Even\n"
    | 1 -> F.printf "Odd\n"
    | _ -> F.printf "Unknown\n" 
  
  in 
  let _ = even_or_odd 0 in
  let _ = even_or_odd 1 in
  let _ = even_or_odd 2 in
  even_or_odd 3

언더바는 와일드 카드로, 어떤 값이든 올 수 있다. 다만 항상 0 혹은 1일 것이므로 이 부분은 필요 없다!

그러나 언더바 부분을 삭제한다면, Ocaml 컴파일러는 에러를 발생시킨다. 패턴매칭이 만족스럽지 않은 이유로 2를 예시로 든다.
우리는 사람으로서 2는 i mod 2 의 결과값이 0 혹은 1인것을 알지만, Ocaml 컴파일러는 알지 못한다.

Ocaml 컴파일러는 ❌expression의 결과값❌이 아닌, ⭕expression의 type⭕으로 패턴 매칭의 완벽함을 체크한다.

그러나 "i mod 2" 의 evaluation result가 integer type이기 때문에, Ocaml은 패턴 매칭이 모든 가능한 정수값(0, 1, 2, 3, ..)을 포함하고 있는지 체크한다. 따라서 2는 커버되지 않고 있으므로 에러를 발생시키는 것이다.
따라서 이 wildcard는 필요하다 !




2) ".." 을 이용한 패턴 매칭

연속적인 문자나 숫자를 가리키기 위해서 .. 을 사용할 수 있다 !

let _ =
  let is_lowercase c =
    match c with
    | 'a' .. 'z' -> true     
    | _ -> false 
  in
  let _ = F.printf "A: %b \n" (is_lowercase 'A') in (*false*)
  let _ = F.printf "A: %b \n" (is_lowercase 'W') in (*false*)
  let _ = F.printf "A: %b \n" (is_lowercase 'b') in (*true*)
  F.printf "A: %b \n" (is_lowercase 'z') (*true*)
  



3) 패턴 매칭 역시, 값을 evaluate하는 expression이다.

let _ =
  let is_lowercase c =
    match c with
    | 'a' .. 'z' -> true
    | _ -> 0
  in
  let _ = F.printf "A: %b \n" (is_lowercase 'A') in (*false*)
  let _ = F.printf "A: %b \n" (is_lowercase 'W') in (*false*)
  let _ = F.printf "A: %b \n" (is_lowercase 'b') in (*true*)
  F.printf "A: %b \n" (is_lowercase 'z') (*true*)
  

따라서 아래와 같은 경우 전자는 true를, 후자는 0을 반환하므로 type이 달라서 ocaml compiler는 에러를 발생시킨다.




4) 패턴 매칭에 tuple과 같은 자료구조 이용 가능

let _ =
  let get_fst p =
    match p with
      | (fst, _) -> fst
  in 
  let get_snd p =
    match p with 
    | (_, snd) -> snd
  in 
  let _ = F.printf "fst: %d\n" (get_fst (1,3)) in (*1*)
  let _ = F.printf "fst: %d\n" (get_fst (2,4)) in (*2*)
  let _ = F.printf "fst: %d\n" (get_snd (1,3)) in (*3*)
  F.printf "fst: %d\n" (get_snd (2, 4)) (*4*)
  

🔔 Example

let _ =
  let calc tup =
    match tup with
    | ('+', x, y) -> x + y
    | ('-', x, y) -> x-+ y
    | ('*', x, y) -> x * y
    | ('/', x, y) -> x / y
    | _ -> failwith "not Supported yet"

  in 
  let _ = F.printf "CalcRes : %d\n" (calc('+', 1, 3)) in (*4*)
  let _ = F.printf "CalcRes : %d\n" (calc('+', 4, 2)) in (*6*)
  let _ = F.printf "CalcRes : %d\n" (calc('-', 1, 3)) in (*-2*)
  let _ = F.printf "CalcRes : %d\n" (calc('-', 4, 2)) in (*2*)
  let _ = F.printf "CalcRes : %d\n" (calc('*', 1, 3)) in (*3*)
  let _ = F.printf "CalcRes : %d\n" (calc('*', 4, 2)) in (*8*)
  let _ = F.printf "CalcRes : %d\n" (calc('/', 1, 3)) in (*0*)
  let _ = F.printf "CalcRes : %d\n" (calc('/', 4, 2)) in (*2*)
  F.printf "CalcRes : %d\n" (calc('%', 1, 3)) in (*Failure*)

0개의 댓글