충남대학교 컴퓨터공학과 이성호 교수님의 "프로그래밍 언어 개론" 강의를 필기한 내용입니다.

다소 잘못된 내용과 구어적 표현 이 포함되어 있을 수 있습니다.

모듈

  • 대문자로 시작함 - 소문자로 해주면 컴파일이 되지 않는다(관례가 아님)
  • 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
  • 이렇게 매개변수는 : 자료형 을 함께 괄호로 묶어주고 리턴타입은 맨 끝에 저렇게 적어주면 된다