[TIL] 함수와 함수형 프로그래밍
Do it! 코틀린 프로그래밍 [첫째마당, 코틀린 기본 익히기] 학습
✏️1. 함수란?✏️
- 여러 값을 입력받아 기능을 수행하고 결괏값을 반환하는 코드의 모음
- 함수를 사용하는 이유? 코드를 재사용할 수 있기 때문
⬇️ 함수의 구조
fun 함수 이름([변수 이름: 자료형]): [반환값의 자료형] {
표현식 ...
[return 반환값]
} // 대괄호([]) 안의 내용은 생략 가능
1-1. 함수 호출과 메모리
- 프로그램이 실행되면 메모리에 프로그램을 위한 공간이 만들어짐
- 함수의 각 정보는 프레임(Frame)이라는 정보로 스택 메모리의 높은 주소부터 채워지고, 생성된 순서의 반대 순서로 소멸됨
⬇️ 함수 호출의 원리
fun main(){
val num1 = 10
val num2 = 3
val result: Int
result = max(num1, num2)
println(result)
}
fun max(a:Int, b:Int) = if (a>b) a else b
1-2. 반환값이 없는 함수와 매개변수
📌 반환값이 없는 함수
- 함수의 반환값이 없을 때 함수의 자료형을 생략하거나 Unit으로 지정 가능
- Unit은 특수한 객체를 반환함 (void는 아무것도 반환하지 않음)
fun printSum(a: Int, b:Int): Unit { } // (1)
fun printSum(a: Int, b:Int) { } // (2) Unit 생략
-> (2)에서 반환값의 자료형이 생략되었지만, 실제로 반환값의 자료형은 Unit임
📌 가변 인자(Variable Argument)
- 함수는 하나만 정의해 놓고 여러 개의 인자를 받을 수 있음
- <vararg> 키워드 사용
fun main(){
normalVarargs(1, 2, 3, 4)
normalVarargs(4, 5, 6)
}
fun normalVarargs(vararg counts: Int){ // counts는 Int형의 배열이 됨
for (num in counts){
print("$num ")
}
print("\n")
}
✏️2. 함수형 프로그래밍✏️
- 코틀린은 함수형 프로그래밍과 객체 지향 프로그래밍을 지원하는 다중 패러다임 언어
- 함수형, 객체지향 프로그래밍은 코드를 간략하게 만들 수 있게 해줌
- 함수형 프로그래밍의 장점
- 코드 간략화
- 테스트나 재사용성이 더 좋아지면서 개발 생산성이 늘어남
2-1. 함수형 프로그래밍이란?
순수 함수를 작성하여 프로그램의 부작용을 줄이는 프로그래밍 기법으로 람다식과, 고차함수를 사용함
📌 순수 함수 (Pure Function)
- 부작용이 없는 함수가 함수 외부의 어떤상태도 바꾸지 않는다면 순수함수 라고 부름
- 스레드에 사용해도 안전하고 코드를 테스트하기에도 쉬움
- 순수 함수의 조건
- 같은 인자에 대하여 항상 같은 값을 반환
- 함수 외부의 어떤 상태도 바꾸지 않음
2-2. 람다식과 고차함수
- 고차 함수는 인자나 반환값에 함수를 사용해 대단히 유연함
- 람다식은 많은 코드들을 간략화하고 함수 자체를 인자나 매개변수로 이용할 수 있어 프로그램의 효율성을 높일 수 있음
📌 일반 함수를 인자나 반환값으로 사용하는 고차 함수
fun main(){
val res1 = sum(3,2)
val res2 = mul(sum(3,3), 3) // 인자에 함수 사용
}
fun sum(a: Int, b: Int) = a + b
fun mul(a: Int, b: Int) = a * b
📌 변수에 할당하는 람다식 함수
- 람다식이 할당된 변수는 함수처럼 사용 가능함
fun main() {
var result: Int
val multi = {x: Int, y:Int -> x * y } // 일반 변수에 람다식 할당
result = multi(10,20) // 람다식이 할당된 변수는 함수처럼 사용 가능
println(result)
}
📌 매개변수에 람다식 함수를 이용한 고차 함수
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)
}
📌 람다식 자료형 생략
val multi: (Int, Int) -> Int = {x: Int, y: Int -> x * y}
val multi = {x: Int, y: Int -> x * y}
val multi: (Int, Int) -> Int = {x, y -> x * y}
val multi = {x, y -> x * y} // !오류! 자료형 추론 불가
📌 반환 자료형이 없거나 매개변수가 하나일 때 람다식 표현
fun main() {
val greet: () -> Unit = { } // 인자와 반환값이 없는 람다식 함수
val square: (Int) -> Int = {x -> x * x} // 매개변수가 하나인 람다식 함수
greet() // 함수처럼 사용 가능
}
📌 람다식의 매개변수가 1개인 경우
- 매개변수가 1개인 경우 화살표 표기를 생략하고 $it 으로 대체 가능
fun main() {
oneParam({ a -> "Hello World! $a"})
oneParam {"Hello World! $it"}
}
fun oneParam(out: (String) -> string){
println(out("OneParam"))
}
2-3. 람다식과 고차함수 호출하기
📌 값에 의한 호출
함수가 또 다른 함수의 인자로 전달된 경우 람다식 함수는 값으로 처리되어 그 즉시 함수가 실행된 후 값을 전달
📌 이름에 의한 람다식 호출
이름이 인자로 전달될 때 실행되지 않고 실제로 호출할 때 실행
📌 참조에 의한 호출 (::)
람다식이 아닌 일반 함수를 또 다른 함수의 인자에서 호출할 때 2개의 콜론(::) 기호를 함수 이름앞에 사용한다
⬇️ 인자와 반환값이 있는 함수
fun main() {
val res1 = funcParam(3, 2, ::sum) // 참조에 의한 호출
println(res1)
// 일반 변수에 값처럼 할당
val likeLambda = ::sum
println(likeLambda(3, 2))
}
fun sum(a: Int, b: Int) = a + b
fun funcParam(a: Int, b: Int, c: (Int, Int) -> Int): Int {
return c(a, b)
}
⬇️ 인자가 없는 함수
fun main() {
hello(::text) // 반환값이 없음
hello({a, b -> text(a, b)}) // 람다식 표현
hello {a, b -> text(a, b)} //소괄호 생략
// 위 3가지 표현은 모두 동일한 결과를 출력
}
fun text(a: String, b: String) = "Hi! $a $b"
fun hello(body: (String, String) -> String): Unit {
println(body("Hello", "World"))
}
✏️3. 코틀린의 다양한 함수✏️
3-1. 익명 함수(Anonymous Function)
- 이름이 없는 일반 함수
- 함수 본문 조건식에 따라 함수를 중단하고 반환해야 하는 경우에 사용
⬇️ 익명 함수 예시
fun(x: Int, y: Int): Int = x + y
val add: (Int, Int) -> Int = fun(x, y) = x + y // (1)
val add = fun(x:Int, y:Int) = x + y // (2)
val add = {x: Int, y: Int -> x + y} // (3)
val result = add(10, 2)
-> (1) 처럼 선언 자료형을 람다식 형태로 써 주면 변수 add는 람다식 함수처럼 add() 와 같이 사용할 수 있다
-> (1),(2),(3) 은 모두 동일한 표현
3-2. 인라인 함수(Inline Function)
- 함수가 호출되는 곳에 함수 본문의 내용을 모두 복사해 넣음
- 함수의 분기 없이 처리되기 때문에 코드의 성능을 높임
- 내용은 대게 짧게 작성
- 인라인 함수는 람다식 매개변수를 가지고 있는 함수에서 동작
- 인라인 함수의 제한
- 인라인 함수의 매개변수로 사용한 람다식의 코드가 너무 길거나 인라인 함수의 본문 자체가 너무 길면 컴파일러에서 성능 경고를 할 수 있음
- 인라인 함수가 너무 많이 호출되면 오히려 코드의 양이 늘어나 좋지 않을 수 있음
⬇️ 인라인 함수 예시
fun main() {
shortFunc(3) { println("First call: $it") }
shortFunc(5) { println("First call: $it") }
}
inline fun shortFunc(a: Int, out: (Int) -> Unit) {
println("Before calling out()")
out(a)
println("After calling out()")
}
코드 상에서는 shortFunc() 함수가 2번 호출되는 것처럼 보이지만
fun main() {
// shortFunc(3) { }
println("Before calling out()")
println("First call: 3")
println("After calling out()")
// shortFunc(5) { }
println("Before calling out()")
println("First call: 5")
println("After calling out()")
}
역컴파일 해보면 shortFunc() 함수의 내용이 복사된 것이다. (위 코드는 역컴파일된 코드가 아님. inline 함수가 어떤식으로 동작하는지 대략적으로 나타냄)
3-3. 확장 함수(Extension Function)
- 필요로 하는 대상에 함수를 더 추가할 수 있는 개념
- 기존 클래스의 선언 구현부를 수정하지 않고 외부에서 손쉽게 기능을 확장
- 동일한 이름의 멤버 함수 혹은 메서드가 존재한다면 항상 확장 함수보다 멤버 메서드가 우선으로 호출
⬇️ 확장 함수 정의
fun 확장 대상.함수 이름(매개변수, ...): 반환값 {
...
return 값
}
⬇️ String 클래스에 확장 함수 추가하기
fun main() {
val source = "Hello World!"
val target = "Kotlin"
println(source.getLongString(target))
}
// String 클래스를 확장해 getLongString() 함수 추가
fun String.getLongString(target: String): String =
if(this.length > target.length) this else target
3-4. 중위 함수(Infix Notation)
- 클래스의 멤버를 호출할 때 사용하는 점(.)을 생략하고 함수 이름 뒤에 소괄호를 붙이지 않아 직관적인 이름을 사용할 수 있는 표현법
- 중위 함수의 조건
- 멤버 메서드 또는 확장 함수여야 한다
- 하나의 매개변수를 가져야 한다
- infix 키워드를 사용하여 정의한다
⬇️ 중위 표현법 예시
fun main() {
// 일반 표현법
val multi = 3.multiply(10)
// 중위 표현법
val multi = 3 multiply 10
}
infix fun Int.multiply(x: Int): Int {
return this * x
}
3-5. 꼬리 재귀 함수(Tail Recursive Function)
- 꼬리 재귀 함수를 통해 스택 오버플로 현상을 해결할 수 있음
- 재귀 함수처럼 스택에 계속 쌓이는 방식이 아닌 꼬리를 무는 형태로 반복
- tailrec 키워드 사용
✏️4. 함수와 변수의 범위✏️
4-1. 함수의 범위
📌 최상위 함수(Top-level Function)
main() 함수의 앞이나 뒤에 선언해도 main() 함수 안에서 함수를 사용하는데 아무런 제약이 없음
📌 지역 함수(Local Function)
함수 안에 또 다른 함수가 선언되어 있는 경우
⬇️ 최상위 함수와 지역 함수
fun a() = b() // 최상위 함수이므로 b() 함수 선언 위치에 상관없이 사용 가능
fun b() = println("b")
fun c() {
fun d() = e() // !오류! d()는 지역함수로 e()의 이름을 모름
fun e() = println("e")
}
fun main() {
a() //최상위 함수는 어디서든 호출 가능
e() // !오류! e()는 c의 블록 밖에서 사용 불가능
}
4-2. 변수의 범위
📌 지역 변수(Local Variable)
- 특정 코드 블록 안에 있는 변수
- 블록을 벗어나면 프로그램 메모리에서 삭제
📌 전역 변수(Global Variable)
- 최상위에 있는 변수
- 프로그램이 실행되는 동안 삭제되지 않고 메모리에 유지
- 코드가 길어지면 전역 변수에 동시 접근하는 코드는 프로그램의 잘못된 동작을 유발
- 자주 사용되지 않는 전역 변수는 메모리 자원 낭비를 불러옴
Author And Source
이 문제에 관하여([TIL] 함수와 함수형 프로그래밍), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@zzangdd/TIL-함수와-함수형-프로그래밍저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)