UPL Ch.1

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

what is OCaml?

Introduction to OCaml

Ocaml은 ML 계열의 언어인 Caml에 객체지향 및 모듈 시스템을 추가한 함수형 프로그래밍 언어이다.

Characteristics of OCaml

Pattern matching

  • Functional programming language

    Ocaml을 포함한 대부분의 함수형 프로그래밍 언어에서, 함수는 first-class values이다.
    이는 함수가 일반 값과 마찬가지로 변수에 할당될 수 있고, 함수의 인자로 전달될 수 있으며,
    함수의 반환값이 될 수 있다는 것을 의미한다.

  • Strongly typed language

    Ocaml은 변수의 타입이 강력하게 지정되어 있는 언어로, 변수의 타입을 명시적으로 지정하고, 이를 강제하며, 타입이 일치하지 않는 연산이나 할당을 방지한다.

  • Type inference

    OCaml에서는 타입 추론 기능을 제공하는데, 타입 시스템이 변수 및 표현식의 타입을 추론할 수 있다.

  • Polymorphism

    OCaml은 다형성을 지원하는 언어로, 데이터 타입을 정의할 때 매개변수의 타입을 일반화한 다음,
    실제 타입이 결정될 때까지 일반화된 상태로 둔다.
    이를 통해 일반화된 함수나 데이터 타입을 다양한 타입의 인수에 대해 동작하도록 만들 수 있다.

  • Pattern matching

    OCaml에서는 패턴 매칭 문법을 사용하여 각각의 Case를 분석할 수 있다.

  • Module System

    OCaml은 객체 지향 언어로, 프로그램을 여러 모듈로 나누어 구현할 수 있다.

이외에도 Data abstract, Inheritance and reuse, Separate compilation
일반적인 객체 지향 언어에서의 개념들도 사용할 수 있다.

Basic of OCaml

Basic informations of OCaml

OCaml의 소스 코드 확장자 : .ml

OCaml은 명시적인 main 함수가 없고, 소스코드를 순차적으로 실행

OCaml 컴파일러 : ocamlc

OCaml 빌드 시스템 : dune

Primitive Types

Int Type / Float Type

int type 내장 연산자 :

Unary arithmentic : -
Binary arithmentic : +, -, *, /, mod
Unary bitwise : lnot
Binary bitwise : lsl, lsr, asl, asr, land, lor, lxor

float type 내장 연산자 :

Unary arithmentic : -.
Binary arithmentic : +., -., *., /., mod

int type <-> float type

int -> float : float_of_int
float -> int : int_of_float

Char Type

Escape sequence

  • '\b' : backspace
  • '\space' : space
  • '\ddd' : the character with ASCII code ddd in decimal
  • '\xhh' : the character with ASCII code hh in hexadecimal
  • '\oOOO' : the character with ASCII code OOO in octal

String Type

String type 내장 연산자

^ : String concatenation

example)
"Hello" ^ "World!" -> "HelloWorld!" : String

.[n] : Random access

example)
"HelloWorld!".[5] -> 'W' : char

유용한 String 라이브러리 모듈

String.length : 문자열 길이 반환

example)
String.length "HelloWorld!" -> 11 : int

String.sub : 서브 문자열 반환

example)
String.sub "HelloWorld!" 5 9 -> "World" : String

Bool Type

Bool Type을 반환하는 비교 연잔자들

x < y : x is less than y
x > y : x is greater than y

x <= y : x is less than or equal to y
x >= y : x is greater than or equal to y

x = y : x equals y (structural equality)
x <> y : x does not equals y (structural equality)

x == y : x is identical with y (physical equality)
x != y : x is not identical with y (physical equality)

How to Compile OCaml?

Ocaml 파일을 컴파일하기 위해서는, 다음과 같은 step을 따른다.
(단, dune, Opam 등 필요한 시스템은 모두 설치되었다고 가정)

  • step 1. 작업 디렉토리 생성
mdkir OCaml_programming
  • step 2. dune 파일 작성
vim dune
dune 파일 내용

(include_subdirs unqualified)
(executable(name hello_world))
  • step 3. OCaml 소스 코드 작성
vim hello_world.ml		
  • step 4. 프로그램 빌드
dune build hello_world.exe		(* dune 파일 내부의 name과 일치해야 함 *)
  • step 5. 프로그램 실행
_build/default/hello_world.exe

※ 다음 명령을 통해 프로그램 빌드 및 실행을 한번에 진행할 수 있다.

dune exec ./hello_world.exe		(* dune 파일 내부의 name과 일치해야 함 *)

Statement / Expression

프로그래밍 언어에서,
Expression어떠한 value를 반환하는 코드 구문이며,
Statement실행 가능한 작업이나 명령어를 나타내는 코드 구문이다.

Pure functional language(순수 함수형 언어)는 Expression으로만 구성되지만,
OCaml은 Pure functional language는 아니므로 매우 제한된 형태의 Expression이 존재한다.

Programming with OCaml

OCaml에서 프로그래밍을 하기 위한 기본기에 대해 정리하였다.

Code comments

Ocaml에서는 multiline comment만 지원한다.

(* ... *)		(* ... 를 주석 처리한다. *)

Standard Input/Output

입력
가장 기본적인 입력 방법은 readline 함수를 이용하는 것이다.
readline 함수는 사용자의 입력을 받아 String 문자열로 반환한다.(linefeed 제외)

출력
가장 기본적인 출력은 Format 모듈의 printf 함수를 이용하는 것이다.
Format.printf를 이용하게 되면 출력 버퍼에 출력 내용이 저장되며,
Format 모듈의 print_flush 함수를 통해 화면에 출력할 수 있다.

flush를 하지 않으면 버퍼가 일정량 채워졌을 때 자동으로 버퍼가 비워져
원하지 않는 결과가 생길 수 있으니, 출력 시에는 flush를 해 주는 습관을 들이는 것이 좋다.

I/O example)
let () =
	Format.printf "Input : ";						(* Gain output buffer *)
	Format.print_flush ();							(* Flush output buffer *)
	let x : string = readline() in					(* Input *)
		Format.printf "The user input is %s\n" x;		(* Gain output buffer *)
    	Format.print_flush ();							(* Flush output buffer *)
in

Variables

variable(변수)는 특정 value에 바인딩 된 식별자를 의미한다.
기본적으로, OCaml에서 모든 변수는 값 변경이 금지되며, 선언과 동시에 바인딩되어야 한다.

변수를 선언하기 위해서는 let-in expression 을 사용한다.

example)
let x : int = 3 in				(* 변수 x의 type은 int, value 3을 바인딩 *)
	begin
		Format.printf "x : %d\n" x;
		Format.print_flush ();
    	(* x = 4; *)				(* 변수 x는 불변이므로 에러 발생 *)
    end

※ 변수를 선언할 때 사용되는 '='는 할당 연산자가 아닌 IsEqualTo 비교 연산자이다.
즉, let x : int = 3; 은 int type의 변수 x와 value 3이 같다는 것을 의미한다.

variable shadowing :
동일 scope 내에 가장 최근에 선언된 변수가 같은 이름의 다른 변수를 가리는 것

example)
let x : int = 3 in			(* 변수 x 선언, x는 3에 바인딩됨 *)
	begin
		let x : int = x + x in			(* 새로운 변수 x 선언, x는 6에 바인딩됨 *)
        	begin
				Format.printf "x : %d\n" x;		(* "x : 6\n"이 버퍼에 저장됨 *)
				Format.print_flush ();			(* x : 6 출력 *)
    		end
    	Format.printf "x : %d\n" x;		(* "x : 3\n"이 버퍼에 저장됨 *)
		Format.print_flush ();			(* x : 3 출력 *)
    end

Bindings

let Binding

기본형 : let [variable] = [expression]

Global let binding은 값에 이름을 붙이는 definition이다.

let Binding을 통해 변수를 선언하거나 함수를 정의할 수 있다.

example)
let x : int = 5;
let y : int = 3 + 7;
let z : string = "Hello";
let w : string = "Hello" ^ " World!";

Wildcard( _ )와 함께 사용하여 expression을 실행하는 용도로도 사용할 수 있다.
Wildcard 사용 : let _ = [expression]

OCaml에서는 사용되지 않는 값을 변수에 바인딩하는 것을 금지하므로,
단순히 Expression을 사용하기 위해 let Binding을 쓸 때에는 wildcard를 사용해야 한다.

example)
let _ =
	begin
		Format.printf "Hello World!";
		Format.print_flush ();
    end

let-in Binding

Local let-in binding은 값에 이름을 붙인 후 세부 expression을 수행하는 expression이다.

기본형 : let [variable] = [expression1] in [expression2]

variable에 expression1의 계산 결과값을 바인딩하고, expression2를 계산할 때 variable이 사용되면 바인딩되었던 값으로 치환하여 계산한다.

example)
let x : int = 3 + 6 in		(* 변수 x에 3 + 6의 계산 결과값인 9를 바인딩 *)
	begin
		Format.printf "x : %d\n" x; 		(* Output buffer에 "x : 9" 를 저장 *)
		Format.print_flush ();			(* x : 9 출력 *)
    end

let-in Binding에서도 let Binding처럼 Wildcard를 이용할 수 있다.

Wildcard 사용 : let _ = [expression1] in [expression2]

example)
let _ = Format.printf "Input : " in
	let _ = Format.print_flush () in
    	let x : int = int_of_string (read_line ()) in
        	begin
        		Format.printf "x : %d\n" x;
            	Format.print_flush ();
            end

Sequencing

OCaml에서 Sequencing이란, 여러 개의 expressions 를 순서대로 실행하는 것을 의미한다.

OCaml에서는 세미콜론으로 연결된 Expressions를 순차적으로 수행하며,
마지막 Expression 뒤에는 세미콜론을 위치시키지 않는다.

🤔 왜 마지막 Expression 뒤에는 세미콜론을 위치시키지 않는가?

세미콜론을 Expression 뒤에 사용하게 되면, 그 다음 표현식이 실행되기 전에
이전 표현식의 결과를 discard한다.
따라서, 마지막 Expression 뒤에 세미콜론을 사용하면 해당 표현식의 결과 값이 반환되지 않으므로, 대부분의 경우 마지막 Expression 뒤에는 세미콜론을 위치시키지 않는다.

example)
let add x y = x + y		(* add 함수는 x + y 를 반환한다 *)
let sub x y = x - y;	(* sub 함수는 결과값을 반환하지 않고 unit 타입으로 처리된다 *)

begin-end expression

begin-end expression을 함께 사용하여 명시적으로 sequencing을 표기하는 것이 일반적이다.
( begin-end expression은 괄호와 동일한 효과를 가짐 )

example)
let x : int = 9 in
	begin			(* begin-end expression *)
		Format.printf "x : %d\n";
		Format.print_flush ();
    end				(* begin-end expression *)

Functions

OCaml에서 함수는 first-class values이므로, 함수 자체가 expression이자 value이다.

따라서, 함수를 다른 함수의 인자로 전달하거나 반환할 수 있다.

기본형 : fun [param_list] -> [expression]
param_list: 인자가 바인딩 될 변수들의 나열 (공백으로 구분)
expression: 함수의 몸체이며, 해당 expression의 계산 결과가 곧 return value이다.

example)
fun x y -> x + y		(* 덧셈 함수 *)
let result = (fun x y -> x - y) 5 3 in	(* 뺄셈 함수 *)
	begin
    	Format.printf "result is %d\n" result;	(* Output buffer에 "result is 2\n" 저장 *)
        Format.print_flush ();	(* result is 2 출력 *)
    end

let definition 혹은 let-in expression을 활용하여 이름있는 함수를 정의할 수도 있다.
이는 문법적 설탕(Syntactic sugar)이며, 내부적으로 익명 함수를 통해 작성된다.

🤔 문법적 설탕이란?

문법적 설탕(Syntactic sugar)이란, 프로그래밍 언어에서 일부 기능이나 문법을
좀 더 직관적이고 간결하게 작성할 수 있도록 제공하는 문법적인 기능을 말한다.
즉, 문법적 설탕은 코드의 가독성을 높이고 작성 속도를 향상시키는 역할을 한다.

example)
let add x y = x + y
(* 내부적으로 let add = fun x -> fun y -> x + y 으로 구현됨 *)

let definition 사용 : let [id] [param_list] = [expression]
let-in expression 사용 : let [id] [param_list] = [expression1] in [expression2]

example)
let add x y = x + y		(* add 함수 정의 *)
let sub x y = x - y in	(* sub 함수 정의 *)
	begin
    	Format.printf "5 - 2 = %d\n" (sub 5 2);	(* Output buffer에 "5 - 2 = 3\n" 저장 *)
        Foramt.print_flush ();	(* "5 - 2 = 3" 출력 *)
    end

함수의 타입은 Arrow 형태로 표기한다 : type -> type

example)
let add (x : int) (y : int) : int = x + y
(* add : int -> int -> int *)

Tuple

Tuple은 연속된 값을 저장하는 자료구조로, C++ STL 및 Python의 Tuple과 동일하다.

Tuple의 타입은 * 기호를 사용하여 표기한다 : type * type
let 또는 let-in을 통해 tuple의 각 원소를 변수에 바인딩할 수 있다.

example)
let tp : int * string = (1, "hello");	(* type of tuple *)
let x, y = (1, 3);				(* x에 1 바인딩, y에 3 바인딩 *)
let a, b = (2, 5) in a + b		(* a에 2 바인딩, b에 5 바인딩 *)

Currying

Currying이란 다중 인자(여러 원소를 가지는 가지는 튜플)를 인자로 받는 함수를
한 번에 하나의 인자만 받는 함수로 분해하는 것을 말한다.

Functional language에서는 Curried form이 일반적이다.

example)
1.
let print ((x, y) : int * int) : unit =		(* Tuples in params *)
  Format.printf "%d %d\n" x y;
  Format.print_flush ();

2.
let print (x : int) (y : int) : unit =		(* Curried Form *)
  Format.printf "%d %d\n" x y;
  Format.print_flush ();

High order function

High order function이란, 함수를 인자로 넘겨받거나 함수를 반환하는 함수를 의미한다.

example)
let add x y = x + y			(* add 함수 정의 *)
let print_func_result (func : int -> int) (x : int) (y : int) : unit =		// 함수를 인자로 받음
  let result : int = func x y in
  	begin
  		Format.printf "result : %d\n" result;
  		Format.print_flush ();
  	end

Recursive function

Recursive function은 자기 자신을 호출하는 재귀 함수를 의미한다.
Recursive function을 사용할 때에는, 반드시 rec 키워드와 함께 named function으로 정의해야 한다.

example)
let rec print_int_list (il : int list) : int list =		(* rec 키워드 사용 *)
	match il with
    | [] -> []
    | hd::tl ->
    	begin
    		print_int hd;
    		print_string " ";
    		print_int_list tl;		(* 재귀 호출 *)
        end

Conditional branch

OCaml에서는 if-then-else expression을 통해 분기문을 작성할 수 있다.

분기문 작성 : if [expression1] then [expression2] else [expression3]

expression1을 계산하여 true인 경우 : expression2를 계산한 값이 전체 expression의 값이다.
expression1을 계산하여 false인 경우 : expression3를 계산한 값이 전체 expression의 값이다.

💡 OCaml은 정적 타입 언어로, 모든 expression이 하나의 type을 가져야 하므로,
expression2expression3의 타입은 동일해야 한다.

profile
주니어 컴공학부생🌱

0개의 댓글