주요글: 도커 시작하기
반응형

마틴 오더스키 교수님이 코세라에서 진행중인 Functional Programming Principles in Scala 강의(https://www.coursera.org/learn/progfun1)의 1주차 요약.


프로그래밍 패러다임


과학의 경우 패러다임은, 어떤 학문 분야에서 구분되는 개념이나 사고 패턴을 설명한다.


프로그래밍 패러다임: 명령형(Imperative) 프로그래밍, 함수형(Functional) 프로그래밍, 로직(Logic) 프로그래밍

(객체 지향 프로그래밍은 이들 패러다임에 orthogonal하다)


명령형 프로그래밍과 한계


명령형 프로그래밍은 다음에 관한 것이다.
  • 변경가능한 변수를 수정하고 할당을 사용하고 (메모리, 로드/저장 명령)
  • 조건문, 루프, 브레이크, 리턴과 같은 제어 구조를 사용 (점프)

순수 명령형 프로그래밍은 명령어를 순차적으로 실행하는 구조로, 단어별로(word-by-word???) 데이터 구조의 개념을 도출하는 경향이 있다.


다항, 콜렉션, 문자열, 문서와 같은 고수준 추상화를 정의할 수 있는 다른 기법이 필요하다.


이론(Theory)이란?


이론은 다음으로 구성된다.

  • 데이터 타입
  • 타입에 대한 연산(오퍼레이션)
  • 값과 연산 간의 관계를 기술하는 규칙

보통, 이론은 변경(mutation)에 대해 묘사하지 않는다. 


변경 없는 이론 예1) 다항식 이론

(a*x + b) + (c*x + d) = (a+c)*x + (b+d) 계수를 변경하는 연산을 정의하지 않음


변경 없는 이론 예2) 문자열 이론의 문자열 연결 연산자

(a ++ b) ++ c = a ++ (b ++ c) 시퀀스 원소 변경 연산자를 정의하지 않음


프로그래밍을 위한 결론

  • 연산자를 위한 이론을 정의하는데 집중
  • 상태 변경을 최소화
  • 연산자를 함수로 여기고, 종종 단순한 함수의 조합으로 처리

함수형 프로그래밍과 언어


 -

 함수형 프로그래밍

함수형 프로그래밍 언어 

 좁은(restricted) 의미

변경가능한 변수, 할당, 루프, 다른 명령형 제어 구조 없는 프로그래밍

변경 가능 변수, 할당, 명령형 제어 구조가 없는 언어 

 넓은(wider) 의미

함수에 집중하는 것 

함수를 이용해서 elegant한 프로그램 을 구성하는 것 


함수형 프로그래밍 언어에서 함수는 일급(first-class)이다.

  • 모든 곳에 정의 가능함 (다른 함수 내부 포함)
  • 다른 값처럼 함수를 파라미터로 전달 가능하고 결과로 리턴될 수 있음
  • 함수를 조합하는 집합 연산자 존재

요즘 함수형이 뜨는 이유


멅티코어와 클라우드 컴퓨티 환경에서 병렬을 활용하는 좋은 방법을 제공하기 때문이다.



프로그래밍의 요소(Elements of Programming)


언어는 다음을 제공한다.

  • 단순한 요소를 표현하는 프리미티브(원시) 식(primitive expression)
  • 식을 묶는 방법
  • 식을 추상화하는 방법(식에 이름을 붙이고 이를으로 참조)

식의 계산(evaluation)


원시 식이 아닌 연산자를 포함한 식은 다음 과정을 따라 계산한다.

  1. 가장 왼쪽 연산자를 선택
  2. 선택한 연산자의 피연산자의 값을 구함(왼쪽 피연산자의 값을 먼저 구함)
  3. 두 피연산자에 연산자를 적용
식에 포함된 이름은 그 이름 정의의 오른쪽으로 대치한다.(예 def name = body 이면, name을 body로 대치)
최종 결과가 값이 될 때까지 과정을 반복한다.

과정 예) pi = 3.14159 이고 radius = 10
(2 * pi) * radious (가장 왼쪽 연산자 선택)
-> (2 * 3.14159) * radius (연산자의 피연산자 값 구함, pi를 3.14159로 대치 후 연산자 적용)
-> 6.28318 * radius (가장 왼쪽 연산자 선택)
-> 6.28318 * 10 (연산자의 피연산자 값 구함, radius를 10으로 대치 후 연산자 적용)
-> 62.8318 (최종 결과로 값 구함)

함수 적용(function application) 계산

파라미터를 가진 함수의 계산 과정은 다음과 같다.
  1. 모든 함수 인자를 왼쪽부터 오른쪽으로 차례대로 계산
  2. 함수 적용을 함수의 우측(함수의 몸체)으로 대치하고, 동시에
  3. 함수의 formal 파라미터를 실제 인자로 대치함
적용 예) 
def square(x: Double) = x * x
def sumOfSquares(x: Double, y: Double) = square(x) + square(y)

sumOfSquares(3, 2+2)
-> sumOfSquares(3, 4) (인자 값 구함)
-> square(3) + square(4) (sumOfSquares 함수의 우측으로 대치 및 파라미터를 인자로 대치)
-> 3 * 3 + square(4) (square 함수의 우측으로 대치)
-> 9 + square(4) (식의 계산에 의해 첫 번째 연산자 식을 계산)
----> 9 + 16 (비슷한 과정 생략)
-> 25

치환 모델(Substitution model)

앞의 식을 계산하는 방식을 치환 모델이라고 부른다. 이 모델의 아이디어는 모든 계산은 식을 한 개의 값으로 reduce한다. 식에 side effect가 없으면 이 모델을 모든 식에 적용할 수 있다.

계산의 끝(termination)

모든 식의 계산이 끝나는 것은 아니다.

예)
def loop: Int = loop
val i = loop()  // loop 식 계산은 끝나지 않음


값 계산의 두 가지 전략

  • call-by-value(이하 CBV): 식을 값으로 바로 계산해서 전달, 모든 함수 인자를 한 번만 계산하는 장점.
  • call-by-name(이하 CBN): 식의 값이 실제로 필요할 때까지 이름으로 전달, 함수에서 사용하지 않는 인자는 함수 몸체를 적용하는 과정에서 계산하지 않는 장점

두 방식은 다음 조건을 충족하는 한 두 방식은 동일한 값으로 reduce된다.

  • reduce한 식이 순수 함수로 구성
  • 둘 다 계산이 끝남

CBV 식이 끝나면 동일한 CBN 식도 끝나지만, 반대는 성립하지 않는다. 아래 코드에서 CBN의 경우 first(1, loop)는 1로 계산되지만 CBV의 경우 두 번째 인자인 loop의 값 계산이 끝나지 않아 식이 끝나지 않는다.


def first(x: Int, y: Int) = x

first(1, loop)


조건 식


스칼라의 if-else은 식이다. if의 predicate은 Boolean 값이다. &&와 ||는 왼쪽 피연산자의 값에 따라 오른쪽 피연산자를 계산하지 않는다. 예를 들어, true || e의 경우 왼쪽이 true이므로 오른쪽의 e 값을 구할 필요가 없으므로 결과는 true가 된다. 이런 식을 "short-circuit evaluation"을 사용한다고 말한다.


중첩 함수


스칼라는 함수 안에서만 접근할 수 있는 중첩 함수를 정의할 수 있다. 특정 함수 안에서만 사용하는 함수를 중첩함으로써 "name-space pollution"을 피할 수 있다.


블록


스칼라의 블록은 중괄호로 구분하며, 정의나 식을 포함한다. 블록 그 자체는 식이므로 식이 올 수 있는 모든 자리에 블록이 올 수 있다.


블록 안에 위치한 정의는 블록 안에서만 보이며, 블록 밖의 같은 이름을 가진 정의를 감춘다. 블록 밖의 정의는 블록 안에서 접근할 수 있다.


val x = 0

val y = 1

def f(y: Int) y + 1

val result = {

    val x = f(3) // 블록 밖의 x를 감춤

    x * x + y // 블록 안의 x임, 블록 밖의 정의에 접근 가능

} + x // 블록 밖의 x


꼬리 재귀(tail recursion)


함수의 마지막 액션이 자신을 호출하면, 함수의 스택 프레임을 재사용할 수 있다. 이를 꼬리 재귀라 한다. 스칼라는 꼬리 재귀를 최적화한다.


def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) // 마지막 액션이 꼬리 재귀, 최적화함


def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n-1) // 마지막 액션이 꼬리 재귀 아님



+ Recent posts