관리 메뉴

개발자비행일지

OCAML 사용자 정의 데이터 타입 본문

▶ 연구일지

OCAML 사용자 정의 데이터 타입

Cyber0946 2020. 4. 3. 11:11

필자가 현재 새로 배우고 있는 OCAML은 정말 강력하면서도, 강력하게 진입장벽을 가지고 있다고 생각한다. 

처음에 사용자 정의 데이터 타입을 어떻게 사용해야 할지 몰라서, 고민에 고민을 하다가 이 자료 저 자료를 종합해서 한글화 한 결과를 개인 연구일지겸 남기고자 이렇게 글로 쓴다.

사용자 정의 데이터 타입익숙해지기 

data2.ml -> 파일 이름 

---------------------------------------------------------------------------------------------------------------------------------------

let t= [1;2;3;4];;

è  *int list*

type gen = Int of int

|Float of float

|Str of string

;;

let t = [Int 10; Float 1.5; Str “hello”];;

è  gen list

let print_gen x =

match x with

Int i -> print_int i

|Float f -> print_float f

|Str s-> print_string s

 

type shape =

Circle of float

|Rect of float * float;;

 

let lst = [Rect (1,0, 2.0); Circle 1.0; Rect(10.0,20.0)];;

 

let area s =

match s with

|Rect (h,w) -> h*.w

|Circle r-> 3.141526 *. r *.r;;

 

type coin = Head |Tail;;

è  enum과 같은 자료형이다.

let c = [Head;head;Tail;Head];;

è  coin list

let rec sum x =

match x with

|[] -> 0

|Head::t -> 1+sum t

|Tail::t->sum t

;;

utop에서

#use “data2.ml”’;; 명령으로 실행한다.

Int 10;; 을 입력하면,

-:gen = Int 10 을 반환한다. 우리가 gen 이라는 데이터 타입을 만든 것이다.

print_gen (List.hd t);;를 실행하면, 가장 먼저 Int 10print_gen 함수에 매개변수로 입력되고, 그 다음 함수의 match문 처리를 통해서 10이 표준 출력 된다.

만약 t라는 gen type 리스트를 전부 출력하고 싶으면 map 라이브러리를 활용할 수 있다. List.map print_gen t;;

출력 결과는 unit list형의 결과를 반환하고, 101.5hello 이런 식으로 붙어서 출력된다.

그 다음 type shape를 통해서 사각형과 원을 표현해 보자

그리고 shape type의 자료형들을 lst 라는 리스트에 담아보자

그 다음 shape type을 통해서 먼저 shape type을 매개변수로 입력 받고, 경우의 수를 Rect 일 경우, Circle일 경우로 나눠주는 함수를 만든다. math s with을 통해 경우를 나누고 , 연산 수행에 있어서는 각 자료형이 가지는 데이터의 연산을 활용한다. 예를 들어, Rect의 경우 우리가 정의할 때, float *float 형으로 정의 했기 때문에 (h,w)이런 식으로 실수가 튜플로 들어온다는 것을 알 수 있다. 그럼 우리가 해줄 수 있는 것은 이 튜플을 이용해서 h *.w 이런 식으로 연산해 줄 수 있다.

Circle일 경우는 float형으로 되어 있기 때문에 r이 왔을 경우 3.14*.r*.r 이런 식의 연산을 통해서 각각의 면적을 구해 줄 수 있다.

그 다음 이 면적을 구하는 것을 전체 lst를 대상으로 하고 싶다면, List.map area lst;;를 해주면 된다.

우리는 사용자 정의 type을 통해서 enum 형태의 자료형을 정의할 수 있다. 먼저

type coin = Head |Tail;; enum 자료형을 만들어 준다. 그 다음 let c = [Head;Head;Tail;Head];; 를 통해서 coin type의 리스트를 만들어 준다.

우리가 이 리스트를 가지고 Head의 개수를 세고 싶다면 다음과 같이 하면 된다.

먼저 함수를 만든다.

let rec sum x =

match x with

|[] -> 0

|Head::t -> 1+sum t

|Tail::t->sum t;;

동작을 설명 하자면 먼저, 재귀 함수이며, 이건 for문이라고 생각하면 된다.

coin 형 리스트에서 각각의 요소들을 match해서  빈 리스트이면 0을 반환하고, Head가 오면 1을 더해준 다음 Head 뒤의 tail 리스트에 대해 sum 함수를 한번 더 실행한다. 그 다음 Tail이 오면 더하지 않고 바로 tail 리스트에 대해 sum 함수를 실행한다. 이 실행은 빈 리스트가 올 때까지 진행된다.

 

linked list를 만들어 보자

type mylist =

Nil|Cons of int * mylist

;;

let t= Cons(10, Nil);; 을 해주면

è  mylist – Cons(10, Nil)이 된다.

è  여기에 리스트를 더 연결해 주소 깊으면 let t = Cons(20, Cons(10, Nil));; 이럭식으로 해주면 된다. 왜냐하면,  Cons int, mylist 형의 튜플로 되어 있기 때문에,  (int, Nil) 또는 (int, Cons(a,b))이런식으로 올 수 있다. -> 여기서 a,baint를 의미하고 b mylist 안의 Nil 이나 Cons를 의미한다.

이제 링크드 리스트를 더해보자

let rec sum lst=

match lst with

| Nil->0

| Cons(a,b) -> a+sum b

;;

가장 먼저  Cons(20, Cons(10, Cons(30, Nil))이런식으로 구성되어 있는 mylist list를 대상으로 합을 구해줄 때, 우리는 lst를 함수의 매개변수로 받아와서, match문을 통해서 이 리스트가 어떤 경우가 있는지 파악한다. lst는 크게 생각해보면 결국 Cons(a,b)의 형태로 구성되어 있다. 리스트가 연결될수록 b가 길어질 뿐이다.

가장 먼저 우리는 Cons(a,b)의 경우 a에 있는 int값을 더해주고 그 다음 뒤에 있는 b를 대상으로 다시 sum 함수를 반복 시켜주면 된다. 결국 링크드 리스트의 마지막 요소로 가지 바로전에는 Cons(x, Nil)을 만나게 되고 x+sum Nil을 하게 된다. 때문에 Nil을 종료 조건으로 잡아서 0을 반환하도록 해주어야 한다.

 

바이너리 트리를 만들어 보자

type tree =

Empty

|Node of tree *int*tree

;;

자 이제 utop Empty;;를 입력하면,

-:tree =Empty가 나온다. 우리가 type tree를 사용해서 빈 트리를 만든 것이다.

자 값을 가지고 있는 트리를 만들어 보자.

Node(Empty, 10, Empty);;를 입력해보자 이것은 10을 값으로 가지는 루트 노드 이다.

 

let t= Node(Empty, 10, Node(Empty, 20, Empty));;

이것은 루트가 10이고 왼쪽은 비었고 우측 자식이 20인 트리이다.

 

let rec sum t=

match t with

|Empty -> 0

|Node(l,v,r) ->v+sum l+sum r ;;

 

type int_option =

None

|Some of int;;

 

위의 int_option은 우리가 기존에 했던 lst head를 뽑아내는 함수를 깔끔하게 만들어 준다.

let hd lst =

match lst with

|[]-> []

|h::_ -> [h];;

이 때, 리스트 형태로 출력하는 것을

let hd2 lst =

match lst with

[] –> None

|h::_ -> Some h

;; 를 만들어 주게 되면 헤드에 해당하는 값이 출력 된다. 만약 입력되는 리스트가 [1;2;3] 이라고 하면 hd 함수는 [1]을 출력하고 hd2의 경우 Some 1 을 출력한다.

 

이런 방식의 접근은 제너릭 타입에 대해서도 가능하다.

type ‘a option =

None

|Some of ‘a;;

è  이 함수는 빌트인 함수이다.

let hd2 lst =

match lst with

[] –> None

|h::_ -> Some h

이 두 함수를 포함한 .ml 파일을 사용하면 우리는 hd2[“a”];;를 했을 때,

è  -: string option = Some “a”를 확인 할 수 있다.

let foo f = match f with

None -> 42.0

|Some n -> n+ .42.0;;

foo 3.3;; 을 입력하면 ??

è  Error

è  우리가 catch with으로 받아온 패턴형태로만 입력이 들어와야 한다.

è  즉 아예 아무것도 안 들어오거나 foo

è  foo (some 3.3) 이런 식으로 들어와야 값이 리턴 된다.

 

자 이제 우리는 타입을 점검하는 프로그램을 만들어 보자

먼저 우리는 아래의 코드를 통해서 아래의 type 규칙을 만족하는지 않하는지 확인해주는 typechecker를 간략하게 구현해 보자.

 

type exp =

    Int of int

  |Bool of bool

  |Add of exp*exp

  |Sub of exp*exp

  |And of exp*exp

  |Or of exp*exp

  |Tobool of exp

  |Toint of exp

type ty = TyInt|TyBool|TyError

è  int 타입, bool 타입 그리고 error 타입을 다룬다.

자 이제 위의 사용자 정의 타입을 가지고 아래의 규칙을 충족하는 지 호가인하는 함수를 만들어 보자.

 

함수를 구현하기 앞서서 어떤 패턴이 있는지 알아보기 위해 규칙들을 해석해 보자.

먼저 int형의 무엇인가 들어오면 이것은 TyInt이다. 마찬가지로 bool 형의 무엇인가 들어오면 이것은 TyBool이다.

둘째로, TyInte1 TyInte2Add하거나 Sub하면 TyInt형이다.

셋째로 TyBoole1e2And 연산하거나 Or 연산하면 TyBool이다.

마지막이로, TyInt e1에대해서 ToBool 연산을 해주면 TyBool이다. 그리고 TyBoole1에 대해서 ToInt e를 해주면 Tyint이다.

let rec type_check exp =

  match exp with

  |Int _ -> TyInt

  |Bool _ -> TyBool

  |Add(e1,e2) |Sub(e1,e2) -> if type_check e1 = TyInt && type_check e2 =TyInt then TyInt else TyError

  |And(e1,e2) |Or(e1,e2) -> if type_check e1 = TyBool && type_check e2 =TyBool then TyBool else TyError

  |Tobool e1 -> if type_check e1 = TyInt then TyBool else TyError

  |Toint e1 -> if type_check e1 = TyBool then TyInt else TyError

 

 

이 것을 종합한 파일은 다음과 같다.

type_checker.ml

type exp =

    Int of int

  |Bool of bool

  |Add of exp*exp

  |Sub of exp*exp

  |And of exp*exp

  |Or of exp*exp

  |Tobool of exp

  |Toint of exp

     

type ty = TyInt|TyBool|TyError

         

let rec type_check exp =

  match exp with

  |Int _ -> TyInt

  |Bool _ -> TyBool

  |Add(e1,e2) |Sub(e1,e2) -> if type_check e1 = TyInt && type_check e2 =TyInt then TyInt else TyError

  |And(e1,e2) |Or(e1,e2) -> if type_check e1 = TyBool && type_check e2 =TyBool then TyBool else TyError

  |Tobool e1 -> if type_check e1 = TyInt then TyBool else TyError

|Toint e1 -> if type_check e1 = TyBool then TyInt else TyError

 

이걸 utop에서 #open “type_checker.ml” ;; 로 불러오고

type_check(Int 1) ;;

type_check(Add ( Int 1, Int 2)) ;;

type_check(Or (Int 1, To Bool(Int 2))) ;; 등의 명령으로 확인 할 수 있다.

 

이제 다시 module이란 부분을 학습하기 위해 또 굴러야겠다...

 

 

'▶ 연구일지' 카테고리의 다른 글

QGroundControl 실행 오류, GLIBC  (1) 2021.11.29
OCAML 사용자 정의 모듈  (0) 2020.04.03
OCAML 자료형  (0) 2020.03.18
OCAML 설치하기  (0) 2020.03.15
Cousera Software Security 5주차 내용 완강  (0) 2020.02.26