[Kotlin] 3. 함수와 함수형 프로그래밍
※ “Do it! 코틀린 프로그래밍” 을 참고하여 작성하였습니다.
03-1 함수 선언하고 호출하기
함수의 구조
[형식]
fun sum(a: Int, b: Int): Int{
var sum= a+b
return sum
}
fun 함수 이름(변수 이름: 자료형, 변수 이름: 자료형, …): 반환 자료형 {
표현식
return 반환값
}
- 인수와 매개변수의 차이
- 인수: 함수를 호출할 때 필요한 변수
- 매개변수: 함수를 선언할 때 참조할 변수
- 인수와 매개변수의 차이
반환값이 없는 함수
- Unit: return 문이 없을 때 반환하는 특수한 객체의 자료형
- 반환 자료형이 없으면 Unit이 생략된 것
- void: 아무 것도 반환하지 않음
- Unit: return 문이 없을 때 반환하는 특수한 객체의 자료형
매개변수 제대로 활용하기
함수의 매개변수에 기본값 지정하기
매개변수 이름과 함께 함수 호출하기
매개변수의 개수가 고정되지 않은 함수 사용하기
- 가변 인자 사용: 하나의 함수에서 여러 종류의 인자를 사용할 수 있음
ex)
1 2 3 4 5 6 7 8 9 10 11
fun main(){ normalVarargs(1, 2, 3, 4) normalVarargs(4, 5, 6) } fun normalVarargs(vararg counts: Int){ for (num in counts){ print("$num ") } print("\\n") }
03-2 함수형 프로그래밍
함수형 프로그래밍: 순수 함수를 작성하여 프로그램의 부작용을 줄이는 프로그래밍 기법
순수 함수
같은 인자에 대해서 항상 같은 결과를 반환하고,
함수 외부의 어떤 상태도 바꾸지 않는 함수
예시
1 2 3
fun sum(a: Int, b: Int): Int{ return a+b }
람다 함수: 이름이 없는 함수, 2개 이상의 입력을 1개의 출력으로 단순화
[람다식]
다른 함수의 인자로 넘기는 함수
함수의 결과값으로 반환하는 함수
변수에 저장하는 함수
1 2 3 4 5 6
val multi= {x: Int, y: Int -> x*y} val multi: (Int, Int) -> Int= {x: Int, y: Int -> x*y} val multi: (Int, Int) -> Int= {x, y -> x*y} val greet: ( ) -> Unit= { println("Hello World!") } val square: (Int) -> Int= {x -> x*x} val nestedLambda: ( ) -> ( ) -> Unit= {{ println("nested") }}
일급 객체
- 일급 객체의 특징
- 일급 객체는 함수의 인자로 전달할 수 있다.
- 일급 객체는 함수의 반환값에 사용할 수 있다.
- 일급 객체는 변수에 담을 수 있다.
- 일급 함수: 함수가 일급 객체
- 일급 함수에 이름이 없는 경우: ‘람다식 함수’, ‘람다식’
- 일급 객체의 특징
고차 함수
다른 함수를 인자로 사용하거나, 함수를 결과값으로 반환하는 함수
다른 함수를 인자로 사용하는 경우
1 2
fun main(){ println(highFunc({x, y -> x+y}, 10, 20))
함수를 결과값으로 반환하는 함수
1
fun highFunc(sum: (Int, Int) -> Int, a: Int, b:Int): Int= sum(a+b)
함수형 프로그래밍의 정의와 특징
- 순수 함수를 사용해야 한다.
- 람다식을 사용할 수 있다.
- 고차 함수를 사용할 수 있다.
03-3 고차 함수와 람다식
고차 함수의 형태
일반 함수를 인자나 반환값으로 사용하는 고차 함수
인자에 일반 함수 사용해보기: mul(sum(3, 3), 3)
반환값에 일반 함수 사용해보기
1 2 3
fun funcFunc(): Int{ return sum(2, 2) }
람다식을 인자나 반환값으로 사용하는 고차 함수
1 2 3 4 5 6 7 8
fun main(){ var result: Int result= highOrder({x, y -> x+y}, 10, 20) println(result) } fun highOrder(sum: (Int, Int) -> Int, a: Int, b: Int): Int{ return sum(a, b) }
인자와 반환값이 없는 람다식 함수
1 2 3 4 5 6 7
fun main(){ val out: ( ) -> Unit= { println("Hello World!") } out() // Hello World! val new= out new() // Hello World! }
람다식과 고차 함수 호출하기
값에 의한 호출
- 람다식 함수()가 다른 함수의 인자로 전달될 경우,
- 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달
이름에 의한 람다식 호출
- 함수() 가 아닌 이름만이 다른 함수의 인자로 전달될 경우,
- 다른 함수를 실행하고 return 때 람다식 함수를 호출하여 실행
다른 함수의 참조에 의한 일반 함수 호출
일반 함수를 다른 함수(고차 함수)에 사용하는 경우- ::(2개의 콜론)을 사용
모두 동일한 표기법
1 2 3
hello(::text) hello({ a, b -> text(a, b) }) hello { a, b -> text(a, b) }
람다식의 매개변수
람다식에 매개변수가 없는 경우
람다식에 매개변수가 1개인 경우
- 1개의 파라미터는 it으로 대체 가능
1 2 3 4 5 6 7 8
fun main(){ oneParam( { a -> "Hello World! $a" }) oneParam { "Hello World! $it" } } fun oneParam(out: (String) -> String){ println(out("One Param")) }
람다식에 매개변수가 2개 이상인 경우
- 2개 이상의 변수들을 다 사용하고 싶지 않으면 _(언더 스코어) 활용
일반 매개변수와 람다식 매개변수를 같이 사용하기
일반 함수에 람다식 매개변수를 2개 이상 사용하기
람다식이 2개이면: 고차함수({첫 번째 람다식}) {두 번째 람다식} 으로 호출 가능
람다식이 3개이면: 고차함수({첫 번째 람다식}, {두 번째 람다식}) {세 번째 람다식} 으로 호출 가능
⇒ 4~ 도 동일한 방식으로 호출
03-4 고차 함수와 람다식의 사례 알아보기
동기화를 위한 코드 구현 구경하기
- ReentrantLock의 객체를 람다식의 함수에 포함시켜서,
- .lock()로 잠그고 임계 영역에 접근
- finally에서 .unlock()으로 다시 잠금을 unlock
네트워크 호출 구현 구경하기
네트워크로부터 무언가를 호출하고, 호출 결과에 따라 특정 콜백함수를 처리
- 콜백 함수: 이벤트가 발생하면 콜백 함수를 호출하여 처리
- 함수 선언 시 try-catch로 네트워크의 성공과 실패를 호출
- 함수 호출 시 인자에 람다식의 구현을 넣기
03-5 코틀린의 다양한 함수 알아보기
익명함수: 이름이 없는 일반 함수
함수 선언 및 호출
1 2 3 4
val add: (Int, Int) -> Int= fun(x, y)= x + y val add= fun(x: Int, y: Int)= x+y val add= { x: Int, y: Int -> x+y } val result= add(10, 2)
람다식에서는 return을 사용하기 어려움,
⇒ return 사용은 4장에서 ~!!
인라인 함수
인라인 함수가 호출되는 곳에 함수 본문의 내용을 모두 복사해서 붙여넣게 됨
[형식] inline fun 함수(매개변수): 반환 자료형 {
…
}
인라인 함수 제한하기
인라인 함수 안에 매개변수가 또 인라인 함수인 경우 성능 경고를 할 수 있음
매개변수들이 함수인 경우, noinline 키워드로 함수를 인라인되지 않게 하기
⇒ 해당 함수는 복사 없이 사용하게 됨
인라인 함수와 비지역 반환
- 인라인 함수에서의 람다식은 return이 가능!!
- 비지역 반환: 호출된 인라인 함수에서의 return으로 바깥 함수가 “반환” 되는 경우
crossinline으로 비지역 반환 금지하기
- 인라인 함수에서의 람다식 내에서 return을 사용하지 않기
- 그대신, 인라인 함수 선언에서의 람다식 앞에 crossinline 키워드 붙이기
⇒ crossinline: 비지역 반환을 금지해야 하는 람다식에 사용
확장 함수
- 클래스에 함수를 추가할 수 있는 함수
[형식]
fun 확장대상.함수 이름(매개변수, …): 반환 값{
…
return 값
}
[호출] 확장대상.함수 이름(매개변수, …)
1 2 3 4 5 6 7 8 9
fun main() { val a= 3 val b= 10 println(a.getMultiply(b)) } fun Int.getMultiply(x: Int): Int { return this * x }
- 특징
- 기존 클래스의 선언 구현부를 수정하지 않고 외부에서 손쉽게 기능을 확장할 수 있음
- 클래스 내의 메소드 이름과 확장 함수의 이름이 같다면, 멤버 메소드 먼저 호출
중위 함수
일종의 연산자를 구현할 수 있는 함수, infix 키워드 사용
중위 함수의 조건
- 멤버 메소드 또는 확장 함수여야 함
- 하나의 매개변수를 가져야 함
- infix 키워드를 사용하여 정의
위의 확장 함수보다 더 직관적으로 표현
1 2 3 4 5 6 7 8
fun main(){ val multi=3 multiply 10 println("multi: $multi") } infix fun Int.multiply(x:Int):Int{ return this*x }
꼬리 재귀 함수
재귀 함수의 조건
- 무한 호출에 빠지지 않도록 탈출 조건을 만들어야 함
- 스택 영역을 이용하므로 호출 횟수를 무리하게 많이 지정해서 연산하면 안됨
- 코드는 간단해야 함
꼬리 재귀 함수
- tailrec 키워드 사용
- return에 다시 꼬리 재귀 함수의 인자를 알맞게 넣음
1 2 3 4 5 6 7 8
fun main(){ val number=5 println("Factorial: $number -> ${factorial(number)}") } tailrec fun factorial(n:Int, run:Int=1):Long{ return if(n==1) run.toLong() else factorial(n-1,run*n) }
1 2 3 4 5 6 7 8 9 10
fun main(){ val n= 100 val first= BigInteger("0") val second= BigInteger("1") println(fibonacci(n, first, second)) } tailrec fun fibonacci(n: Int, a: BigInteger, b: BigInteger): BigInteger{ return if (n==0) a else fibonacci(n-1, b, a+b) }
03-6 함수와 변수의 범위
함수의 범위
- 최상위 함수(사용자 함수): 선언부의 위치에 상관 없이 사용
- 지역 함수: 선언부가 먼저 나와야 사용 가능
- 지역 함수는 {} 블록을 벗어나면 사용하지 못함
- 유도리있게 같은 범위 내에서 사용하기
변수의 범위
- 지역 변수: 특정 코드 블록 안에 있는 변수
- 전역 변수: 최상위에 있는 변수로 프로그램이 실행되는 동안 삭제되지 않고 메모리에 유지됨
⇒ 변수도 마찬가지로 지역 변수면 같은 범위 내에서 변수를 사용하기
단, 지역 변수 허용 범위에서 전역 변수와 이름이 동일한 지역 변수가 존재한다면
지역 변수가 우선시됨