[Kotlin] 함수형 프로그래밍

4 minute read

함수형 프로그래밍

  • 함수형 프로그래밍

    코드 간략, 테스트나 재사용성 증가

    람다식, 고차 함수를 사용해 구성

    순수 함수

  • 객체 지향 프로그래밍

    클래스, 객체들의 관계를 중심으로 코드 작성이 이루어짐

  • 순수 함수

    부작용(side-effect)가 없는 함수

    → 동일한 입력 인자에 대해서는 항상 같은 결과를 출력, 반환한다

      fun sum(a:Int, b: Int): Int{
      	return a+b
      }
    

    동일한 인자 a,b입력받아 항상 a+b를 출력

    조건 ⇒ 같은 인자에 대해 항상 같은 값을 반환

    ⇒ 함수 외부의 어떤 상태도 바꾸지 않음 
    

    ❗ 그렇다면 순수함수가 아닌 것은?

      const val num = 5
    
      fun main(){
      	val result = test(1,2)
      	println(result)
      }
    
      fun result(a: Int, b: Int): Int {
      	return a+b+num
      }
    

    다음 result라는 함수는 입력값과 무관하게 항상 외부의 값을 사용하고 있기 때문에, 동일한 a,b가 들어온다고 해도 외부의 값이 변경되면 값이 변경될 수 있으므로 순수함수가 될수 없음 !

    순수함수를 사용하는 이유

    • 입력과 내용을 분리하고 모듈화 하므로 재사용성 높아짐
    • 함수의 값을 추적하고 예측할 수 있기 때문에 테스트, 디버깅 등이 유리함

람다식

: 일종의 익명함수, 이름 없이사용 및 실행이 가능

→ 고차 함수에서 인자로 넘기거나 결과값으로 반환 등을 할 수 있음

// (Int, Int) -> Int : 람다식의 선언 자료형 
// {a,b 람다식의 매개 변수 -> a * b 람다식의 처리 내용}
val multi : (a:Int, b:Int) -> Int = {a,b -> a*b} //변수를 함수처럼 사용 가능 

val multi = {a: Int, b: Int -> a*b}// 이렇게 간단하게 쓸 수 있음 

multi(2,3)
  • 매개변수가 없을때
val greet: () -> Unit = { print("hi") } //리턴값이 없을땐 Unit 표시 
  • 매개변수가 하나일 때
val add: (Int) -> Int = { it + 1} // it으로 쓸 수 있음 
  • 두줄일때 !
val lambda : () -> Boolean = {
	println("lambda function")
	true //위에 그냥 출력하고 true 를 반환 
} 
  • 값에 의한 호출

    함수가 인자로 전달될 경우, 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달

fun main(){
	val reuslt = callByValue(Lambda()) //람다식 함수를 호출 
	println(result) //3 
}

fun callByValue(b:Boolean):Boolean{ //otherLambda함수 통째로 들어감 
	println("callByValue function") // 2 
	return b
}

fun Lambda: ()-> Boolean = {
	println("Lambda function")// 1 
	true 
}

  • 이름을 사용한 호출

    소괄호 없이 이름으로만 호출 → 람다식 타입으로 써져 있기 때문에, otherLambda 함수 내용이 통째로 callByName의 매개변수로 들어감 ⇒ 해당 함수가 호출되기 전까지(return b()) 람다식 함수가 실행되지 않음

fun main(){
	val reuslt = callByName(otherLambda)//람다식 이름으로 호출 
	println(result) //3 
}

fun callByName(b: ()-> Boolean):Boolean{ //otherLambda함수 통째로 들어감 
	println("callByName function") // 1 
	return b()
}

fun otehrLambda: ()-> Boolean = {
	println("otherLambda function")//2
	true 
}

  • 참조에 의한 호출

    sum이라는 함수는 일반 함수지만, 람다식에 존재하는 매개변수와 반환값과 일치한다면 참조방식으로 사용할 수 있음

fun sum(x :Int, y:Int) = x + y

fun funcParam(a: Int, b: Int, c: (Int,Int) -> Int): Int {
	return c(a,b)
}

funcParam(3,2, ::sum) 
fun sum(a: Int, b: Int) = a + b

fun text(a: String, b: String) = "Hi! $a $b"

fun funcParam(a: Int, b: Int, c: (Int, Int) -> Int): Int {
    return c(a, b)
}

fun hello(body: (String, String) -> String): Unit {
    println(body("Hello", "World"))
}
// 1. 인자와 반환값이 있는 함수
val res1 = funcParam(3, 2, ::sum)
println(res1)

// 2. 인자가 없는 함수
hello(::text) // 반환값이 없음

// 3. 일반 변수에 값처럼 할당
val likeLambda = ::sum
println(likeLambda(6,6))

일급 객체

일급 객체는 함수의 인자로 전달할 수 있다.

일급 객체는 함수의 반환값에 사용할 수 있다.

일급 객체는 변수에 담을 수 있다.

⇒ 코틀린에서 함수는 1급 객체(1급 함수)다.

고차 함수

다른 함수를 인자로 사용하거나 함수를 결과값으로 반환하는 함수

fun main(){
	println(highFunc({a, b -> a + b}, 10, 20))
}

fun highFunc(sum: (Int, Int) -> Int, a:Int, b:Int) : Int{
	return sum(a,b)
}
fun sum(a: Int, b: Int) = a+b 

fun mul(a: Int, b: Int): Int{
	return a * b
}

fun main(){
	val result = mul(sum(2,3), 5)//함수의 특정 인자를 함수로 넘겨줌 
	print(result)
}
//fun funcFunc(a: Int, b: Int): Int{
//	return sum(a,b)
//}

fun funcFunc(a :Int, b:Int) = sum(a,b) //함수 내부의 반환형태를 함수로 넘겨줌 
  • 다양한 고차함수 표현법
fun inc(x: Int) : Int{
	return x + 1
}

fun high(name:String, body: (Int)-> Int):Int{
	println("name: $name")
	val x = 0
	return body(x)
}
// 함수를 이용한 람다식
val result = high("Sean", {  x -> inc(x + 3) })

// 소괄호 바깥으로 빼내고 생략
val result2 = high("Sean") { inc(it + 3) }

// 매개변수 없이 함수의 이름만 사용할 때
val result3 = high("Kim", ::inc)

// 람다식 자체를 넘겨 준 형태
val result4 = high("Sean") { x -> x + 3 }

// 매개변수가 한 개인 경우 생략
val result5 = high("Sean") { it + 3 }

익명 함수

: 이름이 없는 함수

fun (x:Int, y:Int): Int = x + y

val add: (Int, Int) -> Int = fun(x,y) = x + y
val result = add(10,2) 
val add = fun(x: Int, y: Int) = x + y //익명함수 

val add = {x: Int, y: Int -> x + y} //람다식 

❗ 람다식과 익명함수의 차이

익명함수에서는return, break, continue가 사용 가능하지만, 람다식에서는 사용하기 어려움

인라인 함수

: 함수가 호출되는 곳에 내용을 모두 복사, 함수의 분기 없이 처리 → 성능 증가

inline fun shortFunc(){
	println("hi")
}

fun main(){
	shortFunc() // 왔다갔다 이동없이, println("hi")가 바로 복사됨 
}

하지만 shortFunc()의 내용이 길다면 복사하는데 시간이 오래걸리기 때문에, 보통 람다식 매개변수를 가지고있다고 한다.

inline fun shortFunc(a:Int, b: (Int)-> Unit){
	println("hi")
	out(a)
}

fun main(){
	// shortFunc(3, {a -> println("a: $a")}) 왔다갔다 이동없이, println("hi")가 바로 복사됨 
	shortFuc(3) {println("$it")} 
}

확장 함수

: 클래스의 멤버 함수를 외부에서 더 추가할 수 있고, 기존의 표준라이브러리를 수정하지 않고도 확장할 수 있음

만약, 만들고자하는 확장함수와 동일한 이름의 멤버함수가 존재한다면 , 항상 확장함수보다 멤버 메서드가 우선으로 호출됨 !

fun 확장대상.함수명(매개변수, …) : 반환값 {

return 값

}

fun main(){
	val source = "Hello World!"
	val target = "kotlin"
	println(source.getLongString(target))
}

fun String.getLongString(target: String) : String =
	if (this.length > target.lenght) thi else target //this = source 

참고

https://www.boostcourse.org/mo132/joinLectures/28611

Comments