충남대학교 컴퓨터공학과 이성호 교수님의 "프로그래밍 언어 개론" 강의를 필기한 내용입니다.
다소 잘못된 내용과 구어적 표현 이 포함되어 있을 수 있습니다.
모듈
- 대문자로 시작함 - 소문자로 해주면 컴파일이 되지 않는다(관례가 아님)
- import해줄 필요 없음 - dune이 자동으로 모듈화한다
- dune은 굳이 안건들여도 된다 -
(include_subdirs unqualified)
해주면 하위디렉토리까지 전부 모듈화시켜줌
Nested module
- 모듈 내에 모듈을 또 정의해서 사용 가능함
module module_name_ = struct ... end
- nested module은 이렇게 접근 가능하다
Module.Nested_module_.func_
모듈 불러오기
- 기본적으로 import는 할 필요 없다
- 하지만 경로가 길어지면 불편해지므로 open이라는 기능을 지원한다
Module_name_.func_
(* 이건 아래와 같다 *)
open Module_name_
func_
open
을 이용하면 모듈 내의 함수/변수 등을.
로 경로를 들어가서 불러내지 않아도 바로 불러줄 수 있다- 하지만 같은 이름의 변수 / 함수의 경우 충돌이 있으므로 잘 생각해서 사용할 것(충돌이 일어날 경우 그늘효과에 의해 마지막에 불러온 놈으로 앞의 놈까지 다 가려진다)
let-open-in
을 통해 scope를 제한할 수 있다 - 해당 모듈 내에서만 open하여 사용하고 싶을 때- 함수 module을 이용해서도 모듈을 불러올 수 있다 - 모듈의 경로가 긴 경우 이것으로 닉네임을 설정해 사용할 수 있음
module M_ = Module_
- conflict를 막을수도 있고 가독성도 떨어지지 않으므로 추천하는 방향이다
Pattern matching
- 매우 중요하고 유용하다
- 값의 형태에 기반하여 다르게 처리함
- 처리안한 형태가 있을 경우 warning을 뿜는다
match expression_ with
pattern1_ -> expression1_
pattern2_ -> expression2_
pattern3_ -> expression3_
...
- pattern에 선언되지 않은 변수를 사용해도 된다 - expression의 결과가 자동으로 그 변수에 할당됨 (매칭시 어떤 값을 담는 그릇)
- 따라서 변수는 하나의 패턴에만 사용해야 한다(미리선언한 변수를 갖고와서 사용하는것도 안된다 이거야)
- wildcard를 pattern에 넣으면 나머지 전부의 경우의 수를 처리할 수 있다
- 변수를 써도 되지만 이 변수를 다시 사용하지 않으면 warning이 나오므로 사용하지 않을거면 wildcard를 쓰는게 현명하다
- 예상하지 못한 경우의 수가 있을 수 있으므로 항상 마지막엔 wildcard를 pattern에 넣어주는것이 에러가 안난다
- 그리고 처음에 넣어준 expression의 결과값의 type을 보고 모든 가능한 범위가 커버되었는지 확인하므로 논리적으로는 가능한 범위가 커버되었어도 자료형적으로는 모든 범위가 커버되지 않았을 경우가 있다
- 연속된 값을 pattern으로 처리하고 싶을때에는 정규식에서의 [a-b]와 비슷한 연산을 제공한다
[a-b] (* 정규식에서의 이 표현은 *)
'a' .. 'b' (* 이것과 같다 *)
- if-then-else와 마찬가지로 모든 pattern에 대한 매칭값은 자료형이 일관되어야 한다
failwith
- 파이썬에서의 raise와 비슷하다
failwith "error_message"
- 에러를 발생시키고 넣어준 문구를 반환하는 함수이다
- 아주 많은 경우의 수에 대해 각각 복잡한 구현을 해야한다면 하나의 경우의 수 pattern에 넣고 나머지는 failwith로 두고 이런식으로 점진적으로 구현하는 것이 가능하다
리스트
- 원소들의 타입이 반드시 같아야 한다
- 리스트의 타입은 int list, string list 등
type list
형태이다- 이것이 해당 리스트의 자료형 이름이 되는 것
- 리스트의 원소 구분은 세미콜론(;)이다
::
연산자- append_first기능
@
연산자- +기능
- 다행히도 동적배열을 지원한다
List.iter 함수
- 아무것도 반환하지 않는 함수와 리스트를 받아 각 원소들을 함수에 넣어 실행하고 아무것도 반환하지 않음
- args:
- f(function) : 리스트를 받아 뭔가를 실행하고 unit을 반환
- l(list) : 함수에 넣어줄 값들을 원소로 하는 리스트
- return:
- unit : 아무것도 반환하지 않음
List.map 함수
- 함수와 리스트를 받아 리스트 원소들을 함수에 넣어 실행하고 그 결과를 다시 리스트로 묶어 반환
- args:
- f(function) : 원하는 동작을 담은 함수
- l(list) : 함수에 넣어줄 값들을 원소로 하는 리스트
- return:
- list : 함수 실행 결과들을 묶어서 만든 리스트
List.fold_left 함수
- f와 어떤 값x, 리스트l을 받아 f(f(f(x, l[0]), l[1]…)을 실행하는 함수
- args:
- f(function) : 원하는 동작을 담은 함수
- x(whatever) : 어떤 값
- l(list) : 중첩실행할 리스트
- return:
- whatever : 결과값
- 자료형 유의해라 - f, x, l에 어떤 자료형을 넣어야 하는지
리스트 패턴매칭에의 활용
::
연산자는 append_first라는 기능을 하기도 하지만 패턴매칭에서 패턴으로 활용하면 쪼개는 기능으로 활용할 수 있다- pattern을
a :: b
이렇게 적어주면 리스트의 첫번째 원소가 a로 들어가고 첫번째 원소를 제외한 나머리 리스트는 b에 담기게 된다
Disjoint unions
- 여러개의 자료형들을 하나의 자료형으로 묶어서 다양한 형태를 갖는 하나의 자료형을 정의하는 것이다
type type_name_ =
| Identifier1_ of int
| Identifier2_ of string
| Identifier3_ of char
...
- Identifier은 variant라고 불리는데
type_name_
자료형의 한가지 형태라고 볼 수 있다.- 예를들어 number 자료형에 Integer of int라고 명시한다면 Integer 3은 정수로써 기능은 하지만 자료형은 number인 것이다
type_name_
은 반드시 소문자로 시작하여야 한다- 그리고
Identifier
은 반드시 대문자로 시작하여야 한다 - of 자료형은 반드시 적어줘야 하는 것은 아니다 - of 자료형을 적지 않으면 unit을 대체하는 자료형이 생성되는 것이다
Identifier 값
으로type_name_
의 자료형을 하나 생성할 수도 있지만 그 반대도 된다type_name_
을 자료형으로 하는 변수를 만든 다음 이 변수에 값을 넣어주면 이 값의 자료형에 따라Identifier
가 결정되기도 함
- 따라서 pattern matching에서 pattern에 걸리도록 할 수 있다
- type이 모듈이 들어있을때 type의 variant을 사용할때도 모듈이름을 붙여줘야 한다
Disjoint union을 활용한 내장 자료형 - option
- None과 Some의 두 형태를 가짐
- 어따쓰는건지는 나도 잘 모르겠다
함수에서의 자료형 명시
func_name_ (arg1 : type) (arg2 : type) ... :type
- 이렇게 매개변수는
: 자료형
을 함께 괄호로 묶어주고 리턴타입은 맨 끝에 저렇게 적어주면 된다