News Feed

“함수형 프로그래밍에 대한 신선한 접근” 엘릭서 입문

컨텐츠 정보

  • 조회 681

본문

엘릭서는 현재 프로그래밍 언어 분야에서 가장 흥미로운 주제 중 하나다. 얼랭 생태계를 기반으로 만들어진 엘릭서는 함수형 프로그래밍에 대한 새로운 접근과 동시성, 내결함성에 대한 새로운 사고방식을 제안한다.

먼저 엘릭서의 전반적인 구문 특성과 컬렉션을 처리하는 방법을 간단히 알아보고, 이후 패턴 매칭과 루프, 동시성에 대한 접근 방식을 살펴본다. 이는 모든 프로그래밍 언어의 표준 기능이지만 엘릭서는 일반적인 생각과는 다른 접근 방식을 취한다.

엘릭서 시작하기

시작하려면 엘릭서를 설치해야 한다. 설치하면 REPL 모드에서 엘릭서를 실행할 수 있는 IEx CLI가 제공된다. 예제와 같이 콘솔에 직접 입력한다.

예를 들어 주석에는 슬래시를 사용하는 등 모든 언어에는 일상적인 작업을 위한 규칙이 있다. 엘릭서는 다음과 같이 문자열 연결을 처리한다.

"Hello "  "InfoWorld"

이 접근 방식은 더하기(+) 기호를 오버로딩하는 대신 별개의 연산자를 제공한다. 표준 출력으로 출력하려면 다음과 같이 입력한다.

IO.puts("Hello "  "InfoWorld")

IO.puts()는 엘릭서에서 콘솔에 출력하는 방법이다.

이 문자열을 위한 변수를 만들고자 한다면 엘릭서에는 키워드가 필요 없으므로 다음과 같이 선언하기만 하면 된다.

my_var = “Hello Infoworld”

컬렉션

이제 엘릭서의 컬렉션 처리 방법을 보자. 다음은 문자열 컬렉션이다.

books = [  "The Bhagavad Gita",  "Tao Te Ching",  "The Bible",  "The Quran",  "

이 배열 구문(기술적으로는 엘릭서의 List)은 보편적이므로 따로 설명은 필요 없다. 다만 엘릭서에서는 배열이 변경 불가능하다는 점에 유의해야 한다. 이는 전반적인 시스템의 오류 발생을 줄이고 동시성에 더 적합하도록 하는 함수형 프로그래밍의 특징이다.

List는 변경 불가능하지만 엘릭서의 구문으로 addremove와 같은 일부 작업을 수행할 수 있다. 얼핏 보기에는 변경 가능한 것처럼 보이는 방식이지만 내부적으로는 새로운 List가 생성되는 것이다.

books ++ ["The Gospel of John in the Light of Indian Mysticism"]

이제 각 문자열의 문자 수를 세고 그 정수의 컬렉션을 만들려는 경우를 가정해 보자. 여기서 함수형 프로그래밍의 이점을 활용할 수 있다.

book_lengths = Enum.map(books, &String.length/1)

map 함수는 상당히 보편적이며 널리 사용된다. 자바스크립트에서는 컬렉션에 대해 주어진 함수를 실행하는 데 사용된다. 엘릭서는 매핑을 위한 몇 가지 고유한 기능을 제공한다. /1은 어느 버전의 오버로드된 String.length를 사용할지 알려주는데, 여기서는 단일 인수 버전을 사용한다(기술적인 용어로는 아리티(arity)라고 함). & 문자는 String.length 함수에 대한 핸들을 제공하는 “함수 캡처” 연산자다(자바의 메서드 참조 연산자 “::“와 대체로 비슷함).

IO.inspect 함수를 사용해서 보기 좋게 출력할 수 있다.

IO.inspect(book_lengths)[17, 12, 9, 9, 21]

엘릭서의 컬렉션 타입

List 타입은 위에서 이미 살펴봤다. 엘릭서에는 다음과 같은 주요 컬렉션 타입이 포함된다.

  • 목록(List) : 변경 불가하지만 ‘복제에 의해 수정’되도록 설계된 임의의 타입의 동질 컬렉션
    • 구문 : 대괄호와 항목 : [x,y,z]
  • 튜플(Tuple) : 조작이 아닌 값 저장용으로, 목록과 비슷하지만 읽기 성능에 중점을 둔다. 일종의 데이터 액세스 컬렉션이라고 볼 수 있다.
    • 구문 : 중괄호와 항목 : {x,y,z}
  • 키워드 목록(Keywords List) : 순서가 있는 키-값 쌍, 문자열 전용 키, 함수의 명명된 인수에 주로 사용됨
    • 구문 : 대괄호와 쌍 : {x: x1,y: y1,z: z1}
  • 맵(Maps) : 익숙한 키-값 쌍으로, 키는 무엇이든 될 수 있으며 컬렉션에는 순서가 없음
    • 구문 : %중괄호와 쌍:
      • %{x => x1, y => y1, z => z1}
      • %{x: x1, y: y1, z: z1}

맵과 아톰

맵에는 두 가지 선언 스타일이 있는데 그 중에서 무엇을 사용할지는 키가 아톰(atom)인지 여부에 따라 결정된다. 아톰은 값이 이름과 동일한 변수로, 일종의 초상수(super-constant)다. 아톰은 콜론 옆에 리터럴을 붙이는 방식으로 선언된다.

다음과 같이 문자열 키에서 정수 값으로의 맵을 생성할 수 있다.

books_and_lengths = %{ "The Bhagavad Gita" => 17, "Tao Te Ching" => 12 }

다음은 위와 달리 아톰에서 정수로의 맵을 생성하는데, 여기서 우리가 원하는 것은 아니다.

books_and_lengths = %{ "The Bhagavad Gita": 17, "Tao Te Ching": 12 }

콜론의 위치를 보자. Map에서 키 바로 옆에 있는 콜론은 아톰임을 가리킨다. 아톰은 따옴표로 묶을 수 있다(따옴표가 없으면 유효하지 않은 문자를 따옴표를 사용해 지원).

핵심은 일반 변수를 원할 때는 화살표 구문(=>)을 사용하고, 아톰을 원할 때는 키와 콜론(:)을 사용한다는 점이다.

일반적으로 아톰은 다음과 같이 선언된다.

:my_atom

다음은 아톰 키로 맵을 선언하는 또 다른 방법이다.

my_map = %{:atom1 => “foo”, :atom2 => “bar”}

모듈

엘릭서는 모듈을 지원한다. 모델은 관련 함수를 모으는 네임스페이스로, 클래스 또는 코드 블록처럼 상태나 변수를 저장하지 않는다. 동일한 모듈 내에서 다른 함수를 호출할 수 있지만 외부 호출의 경우 호출 앞에 모듈을 붙이거나 모듈을 가져와야 한다.

다음은 간단한 모듈이다.

defmodule BookFunctions do  def myFunc  endendBookFunctions.myFunc()

패턴 매칭

구문의 형태와 표준 라이브러리 특성은 언어 사용의 전반적인 느낌을 형성하는 데 큰 영향을 미친다. 항상 접하게 되는 일반적인 특징이기 때문이다. 그러나 모든 언어에는 구분되는 몇 가지 특징이 있다.

함수형 패턴 매칭은 엘릭서가 제공하는 정교하고 유용한 기능으로, 스위치와 비슷한 구문으로 조건부 함수 실행을 가능하게 해준다. 예를 들어 책 제목의 길이에 따라 small, medium 또는 long을 출력하려는 경우를 가정해 보자.

defmodule BookFunctions do  def categorize_length(length) do    case length do      length when length  "Short"      length when length &lt= 20 -> "Medium"      _ -> "Long"    end  end  def print_categories(lengths) do    Enum.each(lengths, fn length ->      category = categorize_length(length)      IO.puts("#{length} characters: #{category}")    end)  endend

두 가지 참고 사항은 다음과 같다.

  • BookFunctions는 이전에 본 모듈이다.
  • 엘릭서에서 반환 문은 암시되므로 categorize_length() 함수는 마지막 식의 결과를 반환한다.

case 키워드는 categorize_length 함수에서 패턴 매칭 블록을 생성한다. length when length 구문(기술적으로 가드 절)은 length 변수의 범위를 확인할 수 있게 해주며 조건을 충족하면 -> 연산자로 case에서 무엇을 반환할지 알려준다. (함수의 마지막 문이므로 함수의 반환 값이기도 하다.)

다음과 같이 book_lengths에 이 새로운 함수를 사용할 수 있다.

BookBookFunctions.print_categories(book_lengths)17 characters: Medium12 characters: Medium9 characters: Short9 characters: Short21 characters: Long

Enum.each는 자바스크립트와 같은 다른 언어의 forEach와 비슷하며, 컬렉션의 각 요소에 대해 연산을 수행할 수 있게 해준다.

루프

엘릭서에는 for와 while 루프가 없다. 처음에는 당황스러울 수 있지만 함수형 철학에서 선호하는 불변성과 일맥상통하는 부분이다. 본질적으로 엘릭서는 루프 도중의 변형을 지양하며, 대신 재귀 사용을 선호한다. 재귀는 함수 영역에 머무르도록 한다. 이상적으로 보면 순수 함수(즉, 부작용이 없는 함수)를 사용하는 것이 좋다.

필요한 루프 작업의 대부분은 Enum.each, Enum.map과 같은 함수형 연산을 사용해 처리할 수 있다. 엘릭서 포럼에서 루프와 그 대안에 대한 폭넓고 유익한 토론을 볼 수 있다.

내포

for 루프를 가장 직접적으로 시뮬레이션하는 방법 중 하나는 내포(comprehension)인데, 여기서 for를 실제 for 루프로 착각하기 쉽다.

for x do: IO.puts x

내포에 대한 더 자세한 내용과 이를 통해 컬렉션 작업을 간소화하는 방법은 엘릭서 문서에서 볼 수 있다.

파이프 연산자

파이프 연산자는 함수 결과를 연결하기 위한 깔끔한 구문을 제공한다. 중첩 함수의 더 우아한 형태라고 생각하면 된다. 다음은 books_and_lengths 컬렉션에 대한 파이프 연산자의 간단한 예다.

books_and_lengths  |> Map.keys()   |> Enum.map(&String.upcase/1)   |> Enum.join(", ")   |> IO.puts() 

출력은 다음과 같다.

The Bhagavad Gita, Tao Te Ching

동시성

동시성은 복잡한 주제지만 엘릭서가 강점을 가진 영역 중 하나이므로 간단히 살펴보자. 엘릭서는 Actor를 사용한다. Actor는 완전한 운영체제 프로세스가 아니라는 점에서 가상 스레드의 성격이 있다. Actor는 간소화된 동시 통신을 위한 메시지 전달을 지원한다.

엘릭서 문서에서 발췌한 다음 예제는 메시지 처리를 보여준다.

defmodule Example do  def listen do    receive do      {:ok, "hello"} -> IO.puts("World")    end    listen()  endend

listen 함수는 재귀적이므로(마지막에 자기 자신을 호출) 여러 메시지를 처리할 수 있다. 재귀가 없다면 프로세스는 종료될 것이다.

Actor를 시작하려면 spawn을 사용한다.

pid = spawn(Example, :listen, [])

그런 다음 저장된 pid를 사용해서 주 프로세스에서 메시지를 보낼 수 있다.

send pid, {:ok, "hello"}

이렇게 하면 콘솔에 “World”가 출력된다.

결론

언어는 그 언어가 무엇을 쉽게 만들고 어렵게 만드는가에 따라 많은 부분이 정의된다. 엘릭서는 프로그래머가 함수형 프로그래밍 사고방식을 유지하기 쉽게 만들고, 변형과 부작용에 빠지기 어렵게 만드는 데 주력한다.

이를 통해 얻는 전체적인 결과는 언어와 싸우지 않고 협력하는 한 대체로 양질의 함수형 프로그래밍 코드를 작성하게 된다는 것이다. 엘릭서가 많은 관심을 끌고 얼랭의 유산을 현대로 가져온 이유를 이해하기는 어렵지 않다. 엘릭서는 작업을 처리하는 방법에 대한 좋은 아이디어와 활발하고 열정적인 커뮤니티를 갖춘 프로그래밍 언어다.
dl-itworldkorea@foundryco.com

관련자료

댓글 0
등록된 댓글이 없습니다.
Member Rank