erlang 3장
- 프로그래밍 언어/erlang
- 2022. 9. 17.
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 |