대부분 Switch - case문은 특정 패턴에 대해 어떤 동작을 수행해야하는 지를 표현한다.
Ocaml에서는 match-with 표현식을 통해 구현할 수 있다.
Match-with
match-with 표현식은 반환값이 존재한다.
왜냐면 Ocaml에서 대부분 표현식이며, match-with도 예외는 아니기 때문이다.
match-with문은 다른 언어의 패턴 매칭과 정확히는 유사하다고 할 수 있는데, 이유는 더 강력하기 때문이다.
뭐 얼마나 강력하길래 이럴까?
함께 알아보자.
match-with문은 한마디로.. 좀 깐깐하다.
내가 int형 변수에 대한 pattern matching을 하겠다고 match-with문을 사용하면
모든 정수에 대해 어떻게 처리해야하는 지를 명시해야한다.
(* int -> unit *)
let pattern_matching x =
match x with
| 0 -> Format.printf "%d\n" x
| 1 -> Format.printf "%d\n" x+10
위의 match-with문은 컴파일 오류가 발생한다.
Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
Here is an example of a case that is not matched: 2
친절하게 2가 들어오는 경우는 처리하지 못한다고 말해준다.
이건 사실 깐깐하다고 욕할게 아니라, 작성한 사람이 좀 이상하다고 할 수 있다.
특정 케이스 외의 케이스 즉, switch-case의 default같은 것들을 처리해줘야하는게 일반적이기 때문이다.
이 default에 대해 처리하는 방식은 총 두가지로
1. 패턴이 들어올 자리에 변수를 넣는다.
2. 와일드 카드를 사용한다.
위 두가지 방식은 "그 외" 케이스를 처리해준다.
첫번째 방법은 아래와 같이 작성할 수 있다.
let pattern_matching x =
match x with
| 0 -> Format.printf "%d\n" x
| other_case -> Format.printf "Input is not ZERO, this is %d\n" other_case
두번째 방식은 아래와 같이 작성 가능하다.
let pattern_matching x =
match x with
| 0 -> Format.printf "%d\n" x
| _ -> Format.printf "Input is not ZERO!!\n"
차이점은 다른 케이스에 대한 경우를 변수로 받아서 처리하느냐, 아니면 특정한 케이스 외에는 다르게 처리할 것이냐에 따라 다르다.
단 변수로 받을때는 조심해야하는데, 해당 변수를 사용하지 않으면 사용하지 않은 변수라고 오류를 낸다.
switch-case와는 다르게 break같은게 없다.
특정 패턴에 matching된 경우, 그 아래 케이스는 따지지 않는다.
이제 다른 경우를 살펴보자.
let even_or_odd x =
let is_even_number = x mod 2 in
match is_even_number with
| 0 -> Format.printf "This number is even!\n"
| 1 -> Format.printf "This number is odd!\n"
위 코드는 컴파일 오류를 발생시킨다.
왤까? is_even_number는 0 또는 1일텐데, 왜 화를내는걸까.
위에서 설명한 내용에서 눈치를 챘을지도 모르지만, match-with문은 패턴 매칭에 사용하는 값이 아닌 타입을 본다.
let pattern_matching x =
let is_even_number = x mod 2 in
match is_even_number with
| 0 -> Format.printf "This number is even!\n"
| 1 -> Format.printf "This number is odd!\n"
| _ -> failwith "What is this?"
따라서 위와같이 작성해야 컴파일 오류가 발생하지 않는다.
맨 아래 failwith(예외를 발생시키는 expression)까지 절대 도달하지 않지만, 패턴 매칭을 위해서는 꼭 필요한 부분이다.
match-with문은 표현식이라고 했었다.
따라서 반환값을 가지는데, 패턴에 따라서 각각 다른 타입을 반환할 수 없다.
모두 같은 타입으로 반환해야한다.
위의 예시를 보면 int형을 받아서 unit형을 반환(Format.printf의 값은 unit)하는 표현식임을 알 수 있다.
List
Ocaml에서의 리스트는 가지는 원소들의 타입이 모두 같아야한다.
튜플이 아무 타입이나 넣을 수 있는 것과 다르다.
리스트의 타입은 원소의 타입이 무엇인지에 따라 a' list 의 형태로 표기된다.
만약 string 원소가 들어있는 리스트를 인자로 받아 원소들의 합을 반환하는 함수가 있다면, 이 함수의 타입은
string list -> int 이다.
리스트는 대괄호([])를 사용해서 생성하고, 원소들과 같이 생성하려면 세미콜론(;)으로 구분해서 넣어준다.
let int_list = [1;2;3;4]
리스트는 2개의 기본적인 연산자를 가진다.
먼저 '::' 가 있다.
이 연산자는 리스트 앞에 원소를 삽입해서 새로운 리스트를 반환한다.
let int_list = [1;2;3;4]
(* [0;1;2;3;4] *)
let new_int_list = 0 :: int_list
마지막으로 '@'가 있다.
이 연산자는 두 리스트를 연결(concatenate)하여 새로운 리스트를 반환한다.
let int_list = [1;2;3;4]
(* [0;1;2;3;4;5;6;7;8] *)
let new_int_list = [0] @ int_list @ [5;6;7;8]
List에 대한 패턴 매칭
리스트를 소개한건 리스트에 대한 패턴매칭을 소개하기 위함이었다.
리스트는 크게 2가지의 형태를 가진다.
1. 빈 리스트 : []
2. 리스트에 원소가 존재 : first_element :: remainder (first_element = 첫번째 원소, remainder = 나머지 리스트)
let rec sum_list lst =
match lst with
(* 빈 리스트 *)
| [] -> 0
(* 리스트에 원소가 존재하는 경우 *)
| first_element :: remainder -> first_element + (sum_list remainder)
in
Format.printf "%d\n" (sum_list [1;2;3;4;5;6])
리스트에 있는 모든 원소를 더해주는 함수는 위와같이 만들 수 있다.
응용해서 앞에서부터 2개씩 짝지어서 더한 원소들을 역순으로 삽입한 리스트를 만들어보면 다음과 같다.
let rec sum_couple_reverse lst return_lst =
match lst with
(* 빈 리스트 *)
| [] -> return_lst
(* 리스트에 원소가 존재하는 경우 *)
| first_element :: second_element :: remainder -> (
let new_return_lst = return_lst @ [first_element + second_element] in
sum_couple_reverse remainder new_return_lst
)
| _ -> failwith "Odd list"
let _ = List.iter (fun element -> Format.printf "%d " element) (sum_couple_reverse [1;2;3;4] [])
let _ = Format.printf "\n"
let _ = List.iter (fun element -> Format.printf "%d " element) (sum_couple_reverse [1;2;3] [])
let _ = Format.printf "\n"
'언어 > Ocaml' 카테고리의 다른 글
[Ocaml] 함수의 타입에 대해 (0) | 2023.04.05 |
---|---|
[Ocaml] Ocaml의 컨셉에 대해 (2) | 2023.03.24 |