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

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


* 함수형을 잘 모르는 상태에서 요약한 것이므로 내용에 오류가 존재할 수 있음


고차 함수(Higher-Order Function)


함수형 언어는 함수를 일급 값(first-class value)으로 처리한다. 다른 값 처럼 함수를 파라미터로 전달하거나 결과로 리턴할 수 있다. 이는 프로그램을 조합하는 유연한 방법을 제공한다.


파라미터로 다른 함수를 전달받거나 함수를 결과로 리턴하는 함수를 고차 함수(higher order function)라고 부른다.


고차 함수를 사용하면 여러 기능에 출현하는 공통 패턴을 도출할 수 있다. 다음 두 기능은 공통 패턴이 있는데,


def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInt(a + 1, b)

def sumCubes(a: Int, b: Int): Int = if (a > b) 0 else (a * a * a) + sumCubes(a + 1, b)


고차 함수를 이용해서 구현하면 다음과 같이 공통 패턴을 뽑아낼 수 있다.


def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a+1, b)


이 고차 함수를 이용해서 sumInt, sumCubes를 구현한 코드는 다음과 같다.


def id(x: Int): Int = x

def cube(x:Int): Int = x * x * x


def sumInts(a: Int, b: Int): Int = sum(id, a, b) // id 함수를 전달

def sumCubes(a: Int, b: Int): Int = sum(cube, a, b) // cube 함수를 전달


함수는 기본적인 추상인데, 그 이유는 이름을 부여한 명시적인 요소를 사용해서 연산을 수행하기 위한 일반적인 수단을 만들 수 있도록 해주기 때문이다.


임의 함수(anonymous function)


작은 함수를 매번 새로 정의하는 것은 성가신 일인데 임의 함수를 사용해서 이를 해소할 수 있다.


def sumInts(a: Int, b: Int): Int = sum( (x: Int) => x, a, b) // id 함수를 전달

def sumCubes(a: Int, b: Int): Int = sum(x => x*x*x, a, b) // cube 함수를 전달


임의 함수의 파라미터 타입은 컴파일러가 추론가능하면 생략가능하다.


커링(currying)


다음 sum 함수는 (Int => Int) 타입의 함수를 인자로 받아 (Int, Int) => Int 타입의 함수를 리턴한다.


def sum(f: Int => Int): (Int, Int) => Int = {

 def sumf(a,: Int, b: Int): Int = if (a > b) 0 else f(a) + sumf(a+1, b)


  sumf // sumf 함수를 리턴

}


다음은 함수를 리턴하는 sum 함수를 사용해서 구현한 sumCubes이다.


def cubes(x: Int):Int = x*x*x

def sumCubes = sum(cubes)


sum 함수가 cubes를 사용하는 sumf를 리턴하므로, sumCubes(1, 3)은 1 + 8 + 27을 리턴한다. sumCubes 없이 다음과같이 표현할 수도 있을 것이다.


sum(cubes)(1, 3)


함수는 왼쪽에서 오른쪽으로 적용하므로 다음이 성립한다.


sum(cubes)(1,3) == (sum (cubes)) (1,3)


스칼라는 함수를 리턴하는 함수를 위한 다중 파라미터 목록 문법을 제공한다. 앞서 함수를 리턴하는 sum을 다음과 같이 구현할 수 있다.


def sum (f: Int => Int) (a: Int, b: Int): Int =

  if (a > b) 0 else f(a) + sum(f)(a+1, b)


위 함수를 사용하면 sum(cubes) (1, 3)과 같이 함수를 실행할 수 있다.


일반적으로 다중 파라미터 목록을 가진 함수 정의가 있을 때,


def f(args1)...(argsn) = E ( n > 1)


이는 다음과 동등하다.


def f = (args1 => (args2 => ... (argsn => E ) )  )


이런 방식으로 함수를 정의하고 적용하는 것을 커링이라고 부른다. (커링은 Haskell Brooks Cury의 이름에서 딴 것이다.)


함수와 데이터


스칼라는 클래스를 이용해서 데이터를 추상화한다. 클래스 타입의 요소를 객체라고 부르며, 클래스 생성자의 적용(application) 앞에 new 연산자를 위치시켜 객체를 생성한다.


예) new Rational(1, 2)


객체의 멤버는 자바처럼 중위 연산자 "."를 사용해서 선택한다.


데이터를 다루는 함수를 클래스 자체에 넣을 수 있는데 이런 함수를 메서드라고 부른다.


데이터 추상화


다음 세 개의 Rational 클래스를 보자. 클라이언트 입장에서 다음의 두  Rational 클래스는 동일한 데이터를 제공한다.


class Rational(x: Int, y: Int) {

    private def gcd(a: Int, b: Int): Int = ...생략

    private val g = gcd(x, y)

    def numer = x  / g

    def denom = y / g

}


class Rational(x: Int, y:Int) {

    private def gcd(a: Int, b:Int): Int = ....

    val numer = x / gcd(x, y)

    val denom = y / gcd(x, y)

}


두 Rational의 numer와 denom은  클라이언트 입장에서 동일하게 동작한다. 이렇게 클라이언트에 영향없이 데이터의 다른 구현을 선택할 수 있는 것을 데이터 추상화라고 한다.


클래스와 치환 모델


함수를 적용할 때 치환에 기반한 계산 모델을 사용한 것처럼, 클래스와 객체에도 이 모델을 적용한다. 예를 들어, 클래스 다음의 클래스 정의가 있다고 하자.


class C(x1, ..., xn) { def f(y1, ..., yn) = b ..}


이 때 new C(v1, ..., vn).f(w1, ..., wn) 식은 다음과 같이 치환한다.


[w1/y1, ...., wn/yn][v1/x1, ... , vn/xn][new C(v1, ..., v1)/this]b


연산자


파라미터가 한 개인 메서드는 다음과 같이 중위 연산자처럼 사용할 수 있다.


r.add(s) ---> r add s


스칼라의 연산자는 실제로 메서드이며 연산자를 식별자로 사용할 수 있다. 식별자는 다음이 될 수 있다.

  • alphanumeric 식별자: 글자로 시작하고 글자나 숫자가 뒤에 온다.
  • symbolic 식별자: 연산자 심볼로 시작할 수 있고, 다른 연산자 심볼이 올 수 있다.
  • 밑줄('_')은 글자로 처리한다.
  • 밑줄로 끝나는 alphanumeric 식별자 뒤에 연산자 심볼이 위치할 수 있다.

다음은 식별자의 예이다.


xn3   *   ?  list_++   <== ==>


+ Recent posts