erlang 3장

    3.1 모듈

    %%%-------------------------------------------------------------------
    %%% @author user
    %%% @copyright (C) 2022, <COMPANY>
    %%% @doc
    %%%
    %%% @end
    %%% Created : 06. 9월 2022 4:13 PM
    %%%-------------------------------------------------------------------
    -module(geometry).
    -author("user").
    
    %% API
    -export([area/1]).
    
    area({rectancle,Width,Ht}) -> Width * Ht ;
    area({circle, R}) -> 3.14159 * R * R.
    • 모듈은 얼랭에서 코드의 기본 단위다.
    • 모든 함수는 모듈에 담긴다. 
    • 코드를 실행하려면 모듈을 컴파일해야한다. 컴파일 된 모듈은 .beam 확장자를 가진다. 
    • 모듈에서 절은 ";"으로 구분된다. 마지막 절은 "."으로 끝난다.
    • 각 절은 헤드와 본문으로 나누어짐
      • 헤드 : 함수 이름 + 패턴(괄호 안)
      • 본문 : 일련의 식으로 구성됨. 
    • 호출하는 인수가 헤드 패턴과 (함수 이름 + 패턴)과 정확하게 일치할 경우, 패턴은 함수 정의에 나오는 순서대로 매칭된다. 

    3.1.2 컴파일

    # 컴파일
    c(geometry).
    
    # 사용할 때
    geometry:area(인수).
    • 모듈을 사용하기 위해서는 컴파일을 해야함.
    • 컴파일은 c(모듈명).으로 한다.
    • 컴파일하면 .beam 파일이 생성됨

     

    3.1.3 매칭

    -module(geometry).
    -author("user").
    
    %% API
    -export([area/1]).
    
    area({rectancle,Width,Ht}) -> Width * Ht ;
    area({square, X}) -> X * X;
    area({circle, R}) -> 3.14159 * R * R.
    • 여러 개의 절로 구성되는 함수가 존재한다.
    • 어떤 함수에 인자가 전달되면, 순차적으로 함수에 전달된 인수들과 패턴 매칭을 해서 가장 첫번째 매칭되는 절을 실행한다.
    • 위의 장점은 같은 일을 하는 함수가 하나의 절에 있다는 것이다. 
    abstract class Shape{
    	abstract double area();
    }
    
    class Circle extends Shape{
    	final double radius;
        Circle(double radius) {this.radius = radius;}
        double area(){
        	return Math.PI * radiues * radius;
        }
    }
    
    class Sqaure extends Shape {
    	final double side;
        
        Square(double side){
        	this.side = side;
        }
        
        double area(){
        	return side * side;
        }
     }
    • 같은 내용을 자바로 작성할 경우, 각 클래스에 존재한다. 즉, 코드가 서로 다른 곳에 존재한다.

     

     

    3.2 쇼핑으로 돌아가서

    3.2.1 물건 값을 알려주는 함수

    -module(shop).
    -author("user").
    
    %% API
    -export([cost/1]).
    
    cost(oranges) -> 5;
    cost(newspaper) -> 8;
    cost(apples) -> 2;
    cost(pears) -> 9;
    cost(milk) -> 7;
    cost(_) -> 0.
    • 위 모듈을 구성하면, 간단한 패턴 매칭을 실행한다.
    • 모든 절은 하나의 패턴만 가지고, 하나의 패턴이 매칭되는 경우 해당 절을 실행한다.
    • "_" 익명변수는 어떤 것도 일치하지 않는 경우, 일치하게 되어서 0을 출력하도록 도와준다.

     

    3.2.2 토탈 계산 해주는 함수 -> 재귀 사용

    -module(shop1).
    -author("user").
    
    %% API
    -export([total/1]).
    
    total([{What, N}|T]) ->
      shop:cost(What) * N + total(T);
    total([])
      -> 0.
      
      
    %% 예시
    shop:total([
    	{oranges,100},
        {apples,500}
    ])
    • 전달된 값을 리스트 나머지 연산을 이용해서 전체를 계산할 수 있다. 
    • 함수형 언어에는 반복문이 없기 때문에 주로 재귀를 이용한다.
    • 예시
      • orange,100이 Head 패턴 매칭에 걸리게 되어서 처리된다.
      • [{apples,500}]이 나머지 연산에 걸려서 total(T)로 다시 넘어간다. 
      • apples,500이 Head 패턴 매칭에 걸리게 되어서 처리된다.
      • []이 나머지 연산에 걸려서 total(T)로 다시 넘어간다. 
      • 여기서는 패턴 매칭이 []에 되기 때문에 0을 반환한다. 

     

    3.3 이름은 같고 arity가 다른 함수

    sum(L) -> sum(L,0).
    
    sum([], N) -> N;
    sum([H|T], N) -> sum(T, H+N).
    • Arity는 그 함수가 가지는 인수의 수를 의미한다. 
    • erlang에서 이름은 같으나 arity가 다른 함수는 전적으로 다른 함수를 의미한다.
    • 이름은 같고 arity가 다른 함수를 보조 루틴으로 많이 사용한다.
      • 프로그램을 읽는 사람에게 프로그램의 실마리를 제공한다.
      • 이름을 다시 안 지어도 된다.
    • 위는 sum(L)에 리스트가 전달되었을 때, 리스트의 Head를 하나씩 계산하도록 하고 Tail을 추가적으로 계산하도록 하는 방식이다. 
    • 결론은 종료조건이 중요하다.

     

    3.4 Fun

    Myfunc = fun(X) -> 2 * X end.
    • fun은 익명함수를 의미한다. Lambda와 같다고 이해할 수 있음.
    • erlang은 생성한 함수를 변수에 바인딩해서 사용할 수 있다. 즉, 함수 자체를 반환해서 변수에 바인딩해서 쓸 수 있고 다음과 같이 사용한다.
    • fun의 정의가 끝나면 end를 이용해서 끝내주면 된다. 

     

    고차 함수

    • 고차 함수는 fun을 인수로 받거나, 인자로 반환하는 함수를 의미한다.

    fun을 여러개 가지는 고차함수의 동작 방식

    Double_fun = fun(L) -> (fun(X) -> X*L end) end.
    • 위 코드로 fun을 2개 가지는 고차 함수를 선언할 수 있다.
    • Double_fun(L) = fun(X)라는 함수가 된다.
    QQ = Q(10) = Double_Fun(10) = fun(X) -> X * 10 end.
    • 이 때 Q(10)을 하게 되면, L값이 10인 fun(X)가 된다. 즉, QQ는 Q(10)의 익명함수가 된다.
    QQ(100).
    >> 10 * 100 = 1000
    • 이 때 QQ(100)을 하면 위와 같은 식을 통해서 함수가 실행된다.

     

    3.5 간단한 리스트 처리

    double_exec(_, []) -> [];
    double_exec(F, [H|T]) -> [F(H)| double_exec(F, T)].
    • 리스트에는 여러 요소가 있어서 하나씩 처리하기가 꽤 곤란하다. 다음과 같이 재귀 방식으로 처리를 할 수도 있다.
    • 이렇게 반복되는 간단한 리스트 처리를 하는데 특화된 모듈이 있고, 해당 모듈을 이용하면 손쉽게 처리할 수 있다.
    double_exec_map(F, L) -> map(F,L).
    • 다음과 같이 lists 모듈의 map을 이용해서 리스트 요소에 대한 반복 함수 연산을 손쉽게 처리할 수 있다. 

     

     

     

     

    3.6 List Comprehension

    # 기본형
    [F(X) || X <-L]
    
    # 두 가지 형
    [{X,Y} || X <- [1,2,3], Y <- [a,b]].      
    >> [{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]
    • List Comprehension은 리스트에서 Head의 값을 하나씩 뽑아서 리스트를 만들어주는 역할을 한다.
    • List Comprehension 내부에 여러개의 변수를 선언할 경우, 두 가지 형에서 볼 수 있듯이 여러 형태의 for문으로 동작한다. 

     

    Generator / Filter

    • List Comprehension 는 Generator / Filter로 사용된다.
    # 제네레이터
    [X + 2 || X <- L]
    
    # 제너레이터 / 필터 동시 사용
    [X + 2 || X <- L, X > 2]
    • 제네레이터는 기존 리스트에서 변수를 가져올 때 사용한다.
    • 필터는 리스트 컴프리헨션에서 조건절을 추가해서, 조건에 맞는 녀석들만 리스트를 구성할 수 있도록 한다. 

     

    List Comprenhension을 이용한 퀵 정렬

    qsort([Pivot | L]) -> 
    	qsort([X || X <- L, X < Pivot]) 
        ++ [Pivot] ++ 
       	qsort([X || X <- L, X >= Pivot]);
    qsort([]) -> [].

    위 명령어를 이용해서 퀵 정렬을 구현할 수 있다. Pivot > X를 필터로 구성해서 필요한 요소들만 뽑아서 각각 리스트를 만든 다음에 다시 한번 정렬을 하는 방식으로 접근했다. 

     

    3.8 가드

    • 가드는 erlang의 패턴 매칭 능력을 증가시키는데 사용하는 도구다.
    • 가드는 함수의 Header 부분에 "when" 키워드와 함께 선언할 수 있고, 가드가 True인 경우 해당 함수에 패턴 매칭될 수 있다. 
      • 이 때 true는 atom이다. erlang은 boolean type이 없는 대신 true / false라는 atom을 특별하게 사용한다. 
    • 가드는 시퀀스로 나누어진다
    • Or 조건 : Ex1 ; Ex2 ; Ex3 
    • And 조건 : Ex1, Ex2, Ex3
    func1(X,Y) when X > Y ; X =:= Y -> 10000;
    func1(X,Y) -> X + Y * Y.
    • 가드는 다음과 같은 형태로 사용할 수 있다.

     

    가드 술어

    술어 의미
    is_atom(X) X는 아톰이다
    is_binary(X) X는 바이너리다
    is_constant(X) X는 상수
    is_float(X) X는 부동형
    is_function(X) X는 익명함수
    is_function(X,N) X는 N개의 인자를 가지는 익명함수다
    is_integer(X) X는 정수다
    is_list(X) X는 리스트다
    is_number(X) X는 정수 또는 부동형이다
    is_pid(X) X는 프로세스 식별자다
    is_port(X) X는 포트다
    is_reference(X) X는 레퍼런스다
    is_tuple(X) X는 튜플이다
    is_record(X,Tag) X는 형이 Tag인 레코드다
    is_record(X,Tag, N) X는 형이 Tag이고 크기가 N인 레코드다
    • when 절에 올 수 있는 True / False 판단 함수는 다음을 사용할 수 있다.

    가드 빌트인 함수

    기능 의미
    abs(X) X의 절대갑
    element(N,X) X의 N번째 요소, 이 때 X는 튜플.
    float(X) X를 부동형으로 변환. 이 때 X는 숫자여야 함.
    hd(X) 리스트 X의 헤드를 가져온다
    length(X) 리스트 X의 길이
    node() 현재 노드
    node(X) X가 생성되었던 노드, X는 프로세스 / 식별자 / 레퍼런스 / 포트일 수 있음.
    round(X) X를 정수로 변환. 이 때 X는 숫자여야 함.
    self() 현재 프로세스의 프로세스 식별자
    size(X) X의 크기. X는 튜플이거나 바이너리일 수 있음.
    trunc(X) X를 정수로 적살함. 이 때 X는 숫자여야 함.
    tl(X) 리스트 X의 꼬리(tail)

    위 함수를 가드 내에서 사용할 수 있다.

     

     

    3.9 

     

     

     

     

     

     

    3.10 Case와 IF 식

    Case문

    case expression of 
    	pattern1 [when guard1] -> value1;
        pattern2 [when guard2] -> value2;
    end.
    
    %% 예시
    start_case(A,B) ->
      case A+B of
        10 -> io:fwrite("value is 10");
        20 -> io:fwrite("there is no")
      end.
    • case문은 먼저 expression을 수행해서 값을 가져온다.
    • 그리고 그 값이 pattern1에 매칭되는지, pattern2에 매칭되는지에 따라서 수행한다. 
    • 위의 경우 A+B의 값에 따라서 아래 값이 설정된다. 

     

    If 문

    if 
    	Guard1 -> seq1;
        Guard2 -> seq2;
        
    end.
    
    
    
    start_if(A) ->
      if
        A =:= 1 -> 10;
        A =:= 2 -> 20;
        true -> 30
      end.
    • 다음 방식으로 If문을 구성해서 사용한다. 

     

     

    3.11 정상 순서로 리스트 구성하기

    some_function([H|T], Result, I) ->
      H1 = H * I,
      some_function(T, [H1|Result], I + 1);
    some_function([], Result, _) -> Result.
    
    
    =====================================
    Q1 = [1,2,3,4,5]
    e3_17_1:some_function(Q1, [], 1).
    >>>> [25,16,9,4,1]
    • 리스트를 만드는 가장 효율적인 방법은 기존 리스트의 헤드에 값을 추가하는 것이다. (아마도 O(1)로 동작하는 것 같다. )
      • H ++ [T]와 같은 식은 절대로 사용하면 안된다. 얘는 리스트 제일 뒤에 값을 추가하는데, 너무 비효율적이다.
    • 위 형태로 동작 시, H1의 결과값을 Result에 추가하는 형태로 진행된다. 
      • 즉, 다음 순으로 내려가진다.
        • [1] -> [H1 | Result] = [1]
        • [4] -> [H1 | Result] = [4,1]
        • [9] -> [H1 | Result] = [9,4,1]... 
    • 만약 Result의 순서가 중요하다면 list:reverse() 함수를 이용해서 처리할 수 있다. 

     

     

    3.12 두 개의 리스트를 결과로 받기

    • 정수 리스트를 전달하고, 이 정수 리스트에서 짝수와 홀수를 나눈 리스트를 받는다고 가정해보자. 이 때 어떻게 구성해야할까?
    judge_even_odd(L) ->
      Odds = [X || X <- L, X rem 2 =:= 1],
      Evens = [X || X <- L, X rem 2 =:= 0],
      {Odds, Evens}.
    • 첫번째는 위와 같이 구성할 수 있다. 그렇지만 이 경우에는 List Comprehension이 두 번 돌기 때문에 비효율적이다.
    odds_and_evens_acc(L) ->
      odds_and_evens_acc(L, [], []).
    
    odds_and_evens_acc([H|T], Odds, Evens) ->
      case H rem 2 of
        0 -> odds_and_evens_acc(T, Odds, [H|Evens]);
        1 -> odds_and_evens_acc(T, [H|Odds], Evens)
      end;
    odds_and_evens_acc([], Odds, Evens) ->
      {lists:reverse(Odds), lists:reverse(Evens)}.
    • 따라서 코드 상으로는 복잡하지만 다음과 같이 구성할 수 있다. 
    • 위는 동일한 함수가 재귀적으로 호출되지만, 각각 전달되는 인자는 동일하다.
    • 결과적으로 재귀의 종료조건에서 Odds / Evens를 전달해주기 때문에 아무 문제가 없다.

     

     

    erlang의 구분자

    • 세미콜론 : 함수의 절을 구분할 때 사용한다.
    • 마침표 : 전체 함수를 구분하고, 종결을 의미한다
    • 쉼표 : 함수 호출, 데이터 생성자, 패턴에서 인수를 구분한다.

     

     

     

     

    '프로그래밍 언어 > erlang' 카테고리의 다른 글

    erlang : 오류 및 프로세스  (0) 2022.10.12
    erlang : 재귀  (0) 2022.09.24
    erlang 9장 : 병행 프로그램과 오류  (1) 2022.09.21
    erlang 8장 : 병행 프로그래밍  (1) 2022.09.19
    erlang 2장  (0) 2022.09.06

    댓글

    Designed by JB FACTORY