OCaml?
OCaml은 함수형 프로그래밍 언어로, 이름대로 ML 프로그래밍 언어 계열에 속한다.
정적 타입 시스템으로 프로그램의 타입 안정성을 보장하며, 컴파일 시간에 변수의 타입을 추론한다.
함수형 프로그래밍 언어이므로 변수는 기본적으로 불변성을 가진다.
함수가 일급 객체로 취급되어, 함수는 다음의 특징을 가진다.
- 함수 할당: 함수를 변수에 할당할 수 있다.
- 함수 인자: 함수를 다른 함수의 인자로 전달할 수 있다.
- 함수 반환: 함수에서 다른 함수를 반환할 수 있다.
하지만, 상태를 변경하는 명령형 프로그래밍도 가능하며 객체 지향 프로그래밍도 가능하다. 하지만 이는 이 카테고리에서 다루지 않겠다.
정적 타입 언어 (Statically Typed Language)
OCaml은 정적 타입 언어이다. 즉 컴파일 시간에 타입 검사가 수행되고, 런타임 시간에는 타입 관련 문제가 발생할 가능성이 낮다. C언어(정적 타입 언어)와 파이썬(동적 타입 언어)을 비교하면 이해가 쉬울 듯 하다.
정적 타입 언어는 또 다시 두 개로 나눌 수 있다. Type-safe 언어와 Unsafe 언어인데, Type-safe 언어는 컴파일에 성공한 프로그램은 런타임에 타입 오류가 발생하지 않음을 보장한다. OCaml은 Type-safe 언어이다.
정적 타입 언어는 개발 초기 단계에 타입 오류를 발견할 수 있어 안정도가 높고, 실행 시 타입 검사를 생략할 수 있어 실행 효율이 좋다. 그러나 동적 타입 언어에 비해 유연성이 떨어질 수 있다.
기본 구조
OCaml은 일련의 정의로 구성된다. 이 정의들은 각각의 변수에 값을 할당하는 'let' 구문으로 작성된다.
let x1 = e1
let x2 = e2
...
let xn = en
xn은 변수 이름, en은 변수에 할당된 값을 계산하는 표현식이다. 표현식은 절차에 따라 평가된다.
일반 연산
먼저, 일반적인 덧셈/뺄셈/곱셈/나눗셈은 C언어와 같다. (+, -, *, /)
단, 정수끼리의 나눗셈은 몫으로 표현된다. (int / int = int)
나머지는 mod로 표현된다. (a mod b)
불리언 표현식의 대소 비교 연산은 C언어와 같다. ( <, <=, >, >= )
단, is 연산은 =, is not 연산은 <>로 표현한다. (a = b, a <> b)
불리언 표현식은 불리언 연산자로 결합할 수 있다. ( not, &&, || )
데이터 타입
OCaml은 여러 기본 데이터 타입을 제공한다.
int : 정수
float : 부동 소수점
bool : 불리언
char : 문자
string : 문자열
() : 유닛
여기서 유닛은 값이 필요하지 않은 상황에서 쓰이며, 종종 함수의 반환 값으로 쓰인다.
타입 변환
OCaml에서, 서로 다른 타입의 값을 직접 연산하는건 허용되지 않는다. 매우 엄격하기에 int와 float 간의 연산조차 불가능하다.
int를 float로 변환하거나, float를 int로 변환하는 것은 다음과 같이 표현한다.
3 + int_of_float 2.0
3.0 +. float_of_int 2
여기서, float형의 연산은 연산자 뒤에 .을 붙여야 한다.
조건문
OCaml의 조건 표현식은 다음과 같은 구조를 가진다.
if be then e1 else e2;;
be는 조건식, e1은 be가 참일 때 결과, e2는 be가 거짓일 때 결과이다. 이 때, e1과 e2의 타입은 같아야 한다.
변수와 함수
전역 변수
let x = 3 + 4;;
위 코드에서, x 변수에는 7이 바인딩된다.
let 키워드를 통해 전역 변수를 생성할 수 있다.
로컬 변수
let d =
let a = 1 in
let b = a + a in
let c = b + b in
c + c;;
let ... in ... 구문으로 로컬 변수를 생성할 수 있다. 이 구조는 변수의 스코프를 내부 표현식으로 제한한다. 위 식에서 d는 8이 바인딩된다. d 외부에서는 a,b,c를 사용할 수 없다.
함수
let sum_of_squares x y = (square x) + (square y);;
sum of squares 3 4;;
여기서 x와 y는 인자이다. 즉 sum_of_squares 함수는 x와 y를 인자로 받고 x+y를 반환한다.
재귀 함수
let rec factorial a =
if a = 1 then 1 else a * factorial(a-1);;
factorial 5;;
재귀함수는 위와 같이 나타낸다. 함수 이름 앞에 rec을 붙여줘야 한다.
익명 함수
(fun x -> x * x) 2;;
let square = fun x -> x * x;;
익명 함수는 임시적으로 사용되거나 다른 함수에 인자로 전달될 때 유용하다. 변수에 바인딩 할 수도 있다.
일급 객체
앞서 이야기 했듯, OCaml에서 함수는 일급 객체이다. 일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 뜻한다.
따라서 변수에 저장될 수 있고 다른 함수의 인자로 전달될 수 있으며 다른 함수에서 반환될 수 있다.
let plus_a a = fun b -> a + b;;
let f = plus_a 3;;
f 2;;
위의 경우 plus_a의 반환값은 함수이다. 따라서 f에는 plus_a의 인자가 3으로 고정된, 다시 말해 fun b -> 3 + b를 실행하는 함수가 바인딩된다. 따라서 f 2는 5를 반환한다.
패턴 매칭
패턴 매칭은 다양한 값이나 데이터 구조에 대한 조건을 체계적이고 명확하게 처리한다.
let rec factorial a =
match a with
| 1 -> 1
| _ -> a * factorial (a - 1)
위 코드는 팩토리얼을 패턴 매칭으로 나타낸 것이다.
문법은,
match 인자 with
| 조건 -> 결과
...
이다. 여기서 _는 아무거나 들어와도 상관 없다는 뜻이다.
let is_abc c =
match c with
| 'a' | 'b' | 'c' -> true
| _ -> false
위 코드는 a, b, c가 들어오면 true를 반환한다.
타입 추론 및 주석
let sum_if_true test first second =
(if test first then first else 0)
+ (if test second then second else 0);;
위와 같은 함수가 있을 때, OCaml 컴파일러는 다음과 같은 타입 추론을 수행한다.
val sum_if_true : (int → bool) → int → int → int = <fun>
이는 sum_if_true는 인자가 (int → bool), int, int 이고, 반환값이 int인 함수라는 뜻이다.
즉, test라는 인자가 (int → bool)인 함수임까지 추론해낸다.
let sum_if_true (test: int -> bool) (x : int) (y : int) : int =
(if test x then x else 0) + (if test y then y else 0);;
필요한 경우, 명시적으로 타입 주석을 추가할 수도 있다. 만약 타입 주석이 잘못되었다면 컴파일러는 오류를 발생시킨다.
예를 들어, (test: int → int)라고 작성했다면 (test x)는 bool 형을 반환해야 한다는 오류를 내뱉는다.
다형성 함수
# let id x = x;;
val id : 'a -> 'a = <fun>
위 코드에서 id라는 함수는 x를 받으면 그대로 x를 반환하는 매우 간단한 함수이다.
이 경우, x에는 어떤 자료형이 오더라도 상관 없다. 이렇게 여러 타입의 값에 대해 동작할 수 있는 함수를 다형성 함수라 부른다.
let first_if_true test x y =
if test x then x else y
위 함수의 타입을 추론해보자.
먼저 test는 함수로서 x를 받으면 bool 값을 반환한다.
x와 y는 반환값이므로 같은 타입을 가진다.
x는 test 함수에 의해 검사되는 값이고, y는 x가 test 함수에 의해 거짓으로 평가될 때 반환될 대체 값이다.
따라서 위 함수의 타입을 추론하면 다음과 같다.
('a → bool) → 'a → 'a → 'a
다만, test 함수의 구현체가 어느 특정 타입, 예를 들어 int형을 인자로 받아와야 한다면 위 함수의 타입은 다음과 같이 바뀐다.
(int → bool) → int → int → int
튜플
# let x = (1, "one");;
val x : int * string = (1, "one")
# let y = (2, "two", true);;
val y : int * string * bool = (2, "two", true)
튜플은 괄호 안에 쉼표로 구분된 값들로 정의된다. 각 요소는 서로 다른 타입을 가질 수 있다.
let fst p = match p with (x, _) -> x;;
val fst : 'a * 'b -> 'a = <fun>
let snd (_, x) = x;;
val snd : 'a * 'b -> b = <fun>
튜플의 구성 요소는 match 문이나 let 바인딩을 통해 특정 위치의 요소를 가져올 수 있다.
리스트
리스트는 대괄호와 세미클론으로 이뤄진다. 예를 들어, [1; 2; 3]은 정수의 리스트이다.
리스트의 모든 요소는 같은 타입을 가져야 하며, 요소는 순서를 가지고 있다.
리스트의 첫 번째 요소를 헤드, 나머지 요소를 테일이라 부른다.
# 1 :: [2; 3]
- : [1; 2; 3]
# [1; 2] @ [3; 4; 5]
- : [1; 2; 3; 4; 5]
리스트는 cons 연산과 append 연산을 지원한다.
cons 연산 :: 은 요소를 리스트의 앞에 추가한다.
append 연산 @ 은 두 리스트를 연결한다.
let isnil l =
match l with
| [] -> true
| _ -> false
let rec length l =
match l with
| [] -> 0
| _ :: t -> 1 + length t
isnil은 리스트가 비어있다면 true를 반환한다. 여기서 []는 비어있는 리스트를 뜻한다.
length는 리스트의 길이를 재귀적으로 계산하여 반환한다. 여기서 _::t는 리스트의 헤드를 무시하고 테일만 사용할 때 유용하다.
사용자 정의 데이터 타입
열거형 타입 (enum)
# type days = Mon | Tue | Wed | Thu | Fri | Sat | Sun;;
# Mon;
- : days = Mon
각 값은 days 타입의 생성자로, days 타입의 값을 생성할 수 있다.
태그가 있는 유니온 타입 (union)
# type shape = Rect of int * int | Circle of int;;
# Rect (2,3);;
- : shape = Rect (2, 3)
# Circle 5;;
- : shape = Circle 5
태그가 있는 유니온 타입은 하나 이상의 데이터 타입을 하나로 결합한다. 각 생성자는 추가 데이터와 연결된다.
let area s =
match s with
| Rect (w,h) -> w * h
| Circle r -> r * r * 3;;
위 함수는 만약 s가 Rect라면 w * h를 반환하고, Circle이라면 r * r * 3을 반환한다.
Rect와 Circle 모두 shape 타입이기에 가능하다.
인덕티브 데이터 타입 (재귀적)
type mylist = Nil | List of int * mylist;;
이 타입은 비어 있는 리스트 Nil 또는 정수와 다른 mylist의 조합 List를 허용한다.
# Nil;;
# List (1, Nil);;
# List (1, List (2, Nil));;
# let rec mylength l =
match l with
| Nil -> 0
| List (_, l') -> 1 + mylength l';;
List는 (int, mylist) 를 담을 수 있으므로 List (int, Nil), List (int, List (_, _)) 등 재귀적으로 들어올 수 있다.
mylength는 mylist 타입 리스트의 길이를 재귀적으로 계산한다.
예외처리
# let div a b =
try
a / b
with Division_by_zero -> 0;;
try with 문으로 예외처리를 할 수 있다.
예를 들어, 0으로 나누면 Division_by_zero 에러가 발생하는데 위 코드에서는 이 에러가 발생하면 0을 반환한다.
# let div a b =
if b == 0 then raise Problem
else a / b;;
# try div 10 0 with Problem -> 0;;
- : int = 0
raise문은 에러를 발생시킨다. try with문을 사용하여 Problem 에러가 발생하면 0을 반환하도록 처리해주었다.
'학교강의필기장 > 프로그래밍언어론' 카테고리의 다른 글
6. 간단한 언어 만들어보기 - 2 (1) | 2024.04.21 |
---|---|
5. 간단한 언어 만들어보기 - 1 (1) | 2024.04.21 |
4. OCaml - 재귀, 꼬리재귀와 고차함수 (0) | 2024.04.21 |
2. 귀납적 정의 (Inductive Definitions) 2 (0) | 2024.04.16 |
1. 귀납적 정의 (Inductive Definitions) (0) | 2024.03.31 |