함수형 언어
함수형 언어에서 말하는 '함수'는 수학에서 말하는 '함수'를 가지고 프로그램을 만들어보자는 아이디이에서 시작되었다.
우리가 흔히 사용하는 C, C++ 과 같은 언어들은 모두 명령형 언어(imperative languages) 이다.
명령형 언어들은 모두 폰 노이만 아키텍쳐에 직접적인 기반을 두고 있다.
즉, 폰 노이만 구조를 어떻게 쉽게 사용할 지가 목표인 언어였다.
쉽게 사용한다는 말은 효율성이 높다는 의미이다.
효율성이 우선 고려사항이었기 때문에, 소프트웨어 개발에 대한 언어의 적합성을 고려하지 않았다.
함수형 언어(functional languages) 는 수학적 함수에 기반을 둔 언어로서, 프로그램이 실행되는 아키텍쳐가 아니라 사용자에게 더 친숙한 이론적 기반을 사용하는 것에 중점을 둔 언어이다. (덜 친숙한 것 같은데..)
함수형 언어는 domain set과 range set 사이의 요소들을 매핑한 것이다.
수학의 함수를 생각해보면 어떤 argument로 부터 결과가 나온다.
즉, argument - result 사이의 매핑인 것이다.
domain set은 argument의 집합, rage set은 result의 집합이라고 보면 된다.
(정의역, 치역)
Lambda Expression
그런데 사실 함수의 이름 f는 f말고도 다른 이름을 자유롭게 사용해도 된다.
더 나가서는 사실 함수의 이름은 필요없다.
lambda expression (람다 표현식) 은 익명 함수, 아무 이름이나 사용 가능한 함수를 말한다.
함수의 이름은 뭔지 몰라도, 함수의 내용이 중요한 것이다.
람다 표현식은 위와 같이 사용할 수 있으며, 람다 대신 이름을 지어주면 아래와 같이 된다.
람다 표현식은 이름이 없는 함수를 정의한다.
람다 함수를 사용할 때는 람다식 옆에 파라미터를 넣어서 사용한다.
이렇게 사용할 수 있다.
Functional Form
Functional Form 은 더 높은 계층의 함수, 즉 함수 자체를 파라미터로 받을 수 있고, 함수 자체를 결과로 반환할 수 있는 함수를 말한다.
Function Composition
만약 functional form이 두 함수를 인자로 받아서 두번째 함수를 첫번째 함수의 파라미터로 넘기면 아래와 같은 형태를 갖는다.
이렇게 함수를 다른 함수에 적용할 수 있기 때문에, 함수형 언어를 applicative language, 적용형 언어라고도 부른다.
Apply-to-all
functional form은 하나의 함수와 어떤 리스트를 인자로 받고, 그 결과로 리스트 내 모든 원소에 대해 각각 주어진 함수를 적용한 새로운 리스트를 반환할 수 있다.
함수형 언어의 기본 요소
함수형 언어의 디자인 목적은 수학적 함수를 흉내내는 것이다.
따라서 명령형 언어와는 어떤 값을 계산하는 방법이 근본적으로 다르다.
명령형 언어에서는 연산자를 사용하여 값을 계산하면 그 값을 변수에 저장하여 나중에 사용했기 때문에 지난 글에서 본 것처럼 변수에 대해 다양한 고민을 했다.
하지만 함수형 언어에서는 파라미터와 그 결과밖에 없기 때문에 계산을 편하게 하려고 잠깐 사용하는 용도 외에는 변수가 필요하지 않다.
또 함수형 언어는 Referential Transparency (참조 투명성) 특징을 갖는다.
하나의 함수는 똑같은 값이 파라미터로 들어올 때, 항상 같은 결과를 반환한다는 것이다.
즉, 실행 환경에 영향을 받지 않는다. (Reference Environment가 없다.)
(하지만 명령형 언어는 지난 글에서 본 것처럼, 변수 스코프에 따라 함수의 실행결과가 달라질 수 있었다.)
LISP
LISP의 데이터형 : atom / list
List의 형태 : 괄호 안에 sublist, atom이 나열된 형태
원래 LISP는 타입이 없는 언어였다.
Lisp의 list는 내부적으로 하나의 linked-list 에 리스트를 저장한다.
LISP는 인터프리터 언어이다.
함수를 정의할 때는 lambda notation을 이용한다.
이때 함수르 호출하는 것과 리스트의 데이터는 같은 형태를 가진다.
(a, b, c) 라고 하면, a, b, c 3개의 원소를 가진 리스트로서 atom을 나타낼 수도 있고,
a라는 이름의 함수를 b, c 라는 매개변수를 넘겨서 호출하는 것으로 볼 수도 있다.
(LISP에서는 함수 호출 대신 함수 적용, function application 이라고도 한다.)
LISP는 이렇게 단순한 자료구조와 같은 형태의 리스트만 가지고 모든 계산을 다 할 수 있다는 것을 증명했다.
Common LISP
그런데 Lisp가 꽤 간단하다보니 여기에 다양한 부가 기능이 추가된 Lisp 파생언어 (방언) 들이 등장했다.
이 방언들은 특정 컴퓨터에서만 실행할 수 있다거나 하는 문제가 있어서 이들을 하나의 언어로 통합한 Common LISP가 등장했다.
Common Lisp 는 꽤 크고 복잡하기 때문에, 그 중간에 Scheme이라는 언어가 존재한다.
Common Lisp는 records, arrays, 복소수, 문자열, 강력한 IO 기능, access control 이 있는 패키지에 더불어 심지어 반복문도 존재한다.
Common Lisp는 확장성 매크로도 갖고 있다.
매크로는 매크로에 정의된 문자열을 다른 값으로 정의한다. (expand)
그리고 해당 문자열을 사용하면 런타임에 매크로를 정의된 값으로 실행한다. (evaluate)
Common Lisp에서 미리 정의된 함수들은 사실 매크로다.
미리 정의된 함수외에도 DEFMACRO 라는 키워드를 사용하면 사용자 정의 매크로도 만들 수 있다.
Common Lisp는 백틱 (`, backquote) 기호를 사용해서 , 이전의 내용을 그 자체 그대로 나타내도록 할 수 있다.
중간에 있는 식을 연산하지 않고 그 자체 그대로 리스트로 만든다.
, 를 사용하면 그 앞까지만 그대로 리스트로 만들고 그 이후는 연산을 한 결과로 리스트로 만든다.
(중간에 * 3 4 와 같이, Lisp는 prefix notaion을 사용한다. (전위 표기식))
- Reader Macros
C, C++의 preprocess(전처리기) 하고 비슷하다.
C, C++의 전처리기는 매크로로 정의된 식을 expand해주는데, reader Macro가 그 역할을 수행한다.
Lisp를 code representation 으로 확장하는 것이다.
(근데 reader macro도 macro의 특별한 일종이다.)
reader macros는 한글자 문자를 define하는 경우가 많다.
이 문자는 Lisp definition 형태로 expand된다.
또 reader macros 도 사용자 정의로 만들 수 있다.
- Symbol Data Type
reserved word가 있는데, 그 자체로 심볼 데이터 타입이다. (Ruby와 비슷)
심볼은 bound 되기도, unbound 되기도 한다.
parameter symbol은 함수가 실행되는 동안 binding 된다. 마치 명령형 언어의 변수처럼 동작한다.
값이 계산된 경우엔 binding 되어있고, 아니라면 unbinding 되어있는 상태이다.
명령형 언어 & 함수형 프로그래밍
명령형 언어에서 함수형 프로그래밍을 지원하는 경우가 늘고있다.
보통은 함수형 언어의 좋은 점 중, lambda expression 을 언어적으로 지원하는 경우가 많다.
(익명 함수)
대표적인 언어 예시가 자바, C#, python 이다.
파이썬은 함수 자체를 매개변수로 받는 higher-order function 도 지원한다. (filter, map)
그 파라미터로 넘기는 함수는 람다 함수를 많이 사용한다.
또한 function applications 도 부분적으로 지원한다.
이런식으로 사용할 수 있다.
add5(15) 는 15에 5를 더하는 함수로 20을 반환한다.
즉, 미리 알려진 파라미터를 미리 계산해놓을 수 있다는 것을 의미한다.
이를 사용하면 프로그램의 실행효율이 높아진다.
(값을 아는 곳까지는 미리 계산해두기 때문이다.)
Ruby 에서는 block 이라는 것이 있다.
마치 subprogram(함수)처럼 쓰이고, 메서드의 인자로도 전달할 수 있다.
higher-order function을 응용한 higher-order subprogram을 만드는데 사용된다.
각 블록은 lambda 키워드를 사용하면 subprogram으로 바꿀 수 있다.
위와 같이 사용가능하다.
위에서 블록을 함수로 만들어서 times 라는 변수에 넣었는데, 이 변수에 curry 라는 연산자를 사용하여 미리 5를 알려주면 나중엔 3만 넘겨도 알아서 5를 곱한 15가 나오도록 할 수 있다.
명령형 언어와 함수형 언어의 특징을 비교해보면 다음과 같다.
명령형 언어
- 폰 노이만 구조에서 실행되므로 효율적으로 실행된다.
- 복잡한 semantics, syntax를 가진다.
- 동시성을 프로그래머가 직접 설계해야 한다.
함수형 언어
- 간단한 semantics, syntax를 가진다.
- 프로그램을 실행할 때 폰 노이만 구조에서는 비효율적으로 동작하낟.
- 프로그램이 동시성을 자동으로 활용한다.
정리
함수형 프로그래밍 언어는 함수 어플리케이션, 조건식, 재귀, function form 등을 사용하여 프로그램 실행을 제어한다.
Lisp는 순수 함수형 언어로 시작됐지만, 나중엔 명령형 기능도 추가되어 Scheme, Common Lisp 등으로 발전했다.
Haskell 은 lazy functional language 로서, 연산을 나중에 하기 때문에 무한대 리스트, set 축약과 같은 기능을 지원한다.
'CS > 프로그래밍언어론' 카테고리의 다른 글
[프로그래밍언어론] 11. Logic Programming Language (Prolog) (0) | 2024.06.13 |
---|---|
[프로그래밍언어론] 10. Data Type (0) | 2024.06.13 |
[프로그래밍언어론] 8. Name & Binding (0) | 2024.06.12 |
[ Lex/Yacc ] 내가 만든 테스트케이스 (테스트 파일 포함) (0) | 2024.05.08 |
[ lex / yacc ] error: 'AUTO' undeclared (first use in this function) 해결 (0) | 2024.05.03 |