함수형 프로그래밍
함수 선언이 최우선. 데이터의 흐름이 아니라 함수의 선언과 선언된 함수의 유기적인 흐름이 주목적. 핵심은 데이터의 흐름과 값의 변경에 따라 프로그래밍하지 말고 필요한 로직을 함수로 만들어서 함수들의 흐름에 따라 프로그래밍하자는 개념이다.
주요 원칙
-
일급 객체(First Class Citizen)로서의 함수
일급 객체란 함수가 프로그램 최상위 구성요소라는 의미. 클래스를 선언하지 않고도 함수를 정의할 수 있어야 하며, 함수 내에 다양한 구성요소(다른 함수, 클래스)를 포함할 수 있어야 한다. 또한, 함수를 변수처럼 사용할 수 있어야 한다. 함수의 인수로 함수를 전달할 수 있어야 하고, 함수를 반환할 수 있어야 한다.
// 다양한 구성요소를 포함하는 함수 fun superFun() { val superData = "Hello" fun subFun() { // ... } class SubClass { fun subClassFun(){ // ... } } subFun() SubClass().subClassFun() } // 람다식의 함수 대입 val funVal1 = { x1: Int -> println("Hello World") x1 * 10 } funVal1(10) // Function Reference 대입 fun someFun(){ println("I am some Function") } val funVal2 = ::someFun // :: - 멤버 레퍼런스 funVal2()
-
순수 함수(Pure Function)로 정의되는 함수
순수 함수로 정의되는 함수는 부수효과(Side-Effect)가 발생하지 않는 함수이다. 이는 같은 인수를 전달해서 항상 같은 결과값을 반환한다는 의미이다. 순수 함수가 되려면 함수 내에서 함수 밖의 데이터를 변경하는 작업이 발생하지 않아야 하며, 별도의 입출력이 발생하지 않아야 한다.
비즈니스 로직을 전산화하는 프로그램에서는 순수 함수 100%는 불가능하다. 데이터는 파일, DB, 네트워크 등에 입출력이 일어나기 때문이다. 많은 부분을 순수 함수로 작성해야하는게 관건이다.
// 랜덤 값이 발생하는 함수 fun some(a: Int): Int { return (Math.random() * a).toInt() } // 파일 입출력이 발생하는 함수 fun foo(a: String): Boolean { try { val file = File("foo.txt") val out = FileWriter(file) out.write(a) out.flush() return true } catch (e: Exception){ return false } } // 순수 함수 fun some(a: Int, b: Int): Int { return a + b } // 매개변수가 없는 함수 - 의미없는 순수 함수 fun some(): Int { return 10 } // 반환값이 없는 함수 - 의미없는 순수 함수 fun some(a: Int, b: Int) { val result = a + b }
데이터
- 데이터는 변경되지 않으며 프로그램의 상태만 표현한다. (데이터 불변성)
- 함수에서 데이터는 변경하지 않고 새로운 데이터를 만들어 반환한다.
이점
- 코드가 간결하여 개발 생산성과 유지 보수성이 증대된다.
-
동시성 작업을 좀 더 쉽고 안전하게 구현할 수 있다.
소프트웨어의 규모가 커지고 처리량이 방대해지다 보니 멀티코어 프로세싱이 기본이 된 상황이다. 순수 함수를 이용하고 부수효과를 줄이므로 쓰레드의 안전성을 확보할 수 있고 병렬처리에 도움을 준다.
람다 표현식
익명 함수를 지칭하는 용어이다. 코드를 간결하게 하기 위해 자주 이용되고 고차 함수의 매개변수나 반환값으로 사용된다.
{ 매개변수 -> 함수내용 }
- 람다 함수는 항상 {} 으로 감싸서 표현해야 한다.
- {} 안에 -> 표시가 있으며 -> 왼쪽은 매개변수, 오른쪽은 함수 내용이다.
- 매개변수 타입을 선언해야 하며 추론할 수 있을 때는 생략할 수 있다.
- 함수의 반환값은 함수 내용의 마지막 표현식이다.
val sum = { a: Int, b: Int -> a + b } // 정의 후 바로 호출 { println("Hello") }() // 함수 타입 선언 val lambdaFun: (Int) -> Int = { x: Int -> x * 10 } // 함수 타입 정의 typealias MyType = (Int) -> Boolean val myFun: MyType = { it > 10 } // it 활용 - 함수 타입이 선언된 곳에서만 사용 val lambdaFun: (Int) -> Int { x -> x + 10} val itLambdaFun: (Int) -> Int { it + 10 } val lambdaFun1: (User) -> Int = { user:User -> user.age } val lambdaFun2: (User) -> Int = { it.age } // 멤버 참조 val lambdaFun3: (User) -> Int = User::age