반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 검색 기능 확장
- 해시
- 가장 큰 수
- 영속 자료구조
- 루씬 인 액션
- 정렬
- 고차원 함수
- @Getter
- Java
- 프로그래머스
- @Data
- @EnableScheduling
- K번째수
- 전화번호 목록
- 코딩 테스트
- @configuration
- @Setter
- 크론 표현식
- 스택/큐
- kubenetes
- 모던 자바 인 액션
- H-index
- 쿠버네티스
- 기능개발
- 다리를 지나는 트럭
- 롬복 어노테이션
- 알고리즘
- 커링
- 스프링 스케쥴러
- 완주하지 못한 선수
Archives
- Today
- Total
Today I Learned
코틀린에서 구현하는 유창성 (2) 본문
728x90
함수 확장
- 코틀린의 Function은 andThen() 메소드를 가지고 있지 않지만, 인젝트해서 사용할 수 있다.
fun <T, R, U> ((T) -> R).andThen(next: (R) -> U): (T) -> U =
{ input: T -> next(this(input)) }
fun increment(number: Int): Double = number + 1.toDouble()
fun double(number: Double) = number * 2
val incrementAndDouble = ::increment.andThen(::double)
println(incrementAndDouble(5)) //12.0
- ::구문을 이용한 increment()의 참조에 andThen() 메소드를 호추랬고, double() 메소드에 참조를 넘겼다.
- 그 결과 increment()와 double()을 합친 함수가 된다.
influx를 이용한 중위 표기법
- 'if(obj instanceof String)'처럼 연산자가 중간에 있거나 피연산자 사이에 있는 것을 중위표기법이라 한다.
- Java에서는 정의된 연산자에서만 제한적으로 사용할 수 있지만, 코틀린에서는 코드에 중위표기법을 사용할 수 있다.
- 연산자는 항상 자동으로 중위표기법을 사용하지만, 메소드는 infix 어노테이션을 사용해야 한다.
operator infix fun Circle.contains(point: Point) =
(poin.x -cx) * (point.x - cx) + (point.y - cy) * (point.y - cy) <
radius * radius
println(circle.contains(point1)) //true
println(circle contains point1) //true
Any 객체를 이용한 자연스러운 코드
- 코틀린에는 코드를 간결하고 표현력있게 만들어주는 4가지의 특별한 메소드들이 있다.
- also(), apply(), let(), run()
4가지 메소드의 동작
val format = "%-10s%-10s%-10s%-10s"
val str = "context"
val result = "RESULT"
fun toString() = "lexical"
println(String.format("%-10s%-10s%-10s%-10s%-10s",
"Method", "Argument", "Receiver", "Return", "Result"))
println("===============================================")
val result1 = str.let { arg ->
print(String.format(format, "let", arg, this, result))
result
}
println(String.format("%-10s", result1))
val result2 = str.also { arg ->
print(String.format(format, "also", arg, this, result))
result
}
println(String.format("%-10s", result2))
val result3 = str.run {
print(String.format(format, "run", "N/A", this, result))
result
}
println(String.format("%-10s", result3))
val result4 = str.apply {
print(String.format(format, "apply", "N/A", this, result))
result
}
println(String.format("%-10s", result4))
- 4개의 메소드 모두 전달받은 람다를 실행시킨다.
- let()과 run()은 람다를 실행시키고 람다의 결과를 호출한 곳으로 리턴한다.
- also()와 apply()는 람다의 결과를 무시하고 컨텍스트 객체를 호출한 곳으로 리턴한다.
- run()과 apply()는 run()과 apply()를 호출한 컨텍스트 객체의 실행 컨텍스트를 this로 사용하여 실행시킨다.
장황하고 지저분한 코드로부터
class Mailer {
val details = StringBuilder()
fun from(addr: String) = details.append("from $addr...\n")
fun to(addr: String) = details.append("to $addr...\n")
fun subject(line: String) = details.append("subject $line...\n")
fun body(message: String) = details.append("body $message...\n")
fun send() = "...sending...\n$details"
}
val mailer = Mailer()
mailer.from("builder@agiledeveloper.com")
mailer.to("venkats@agiledeveloper.com")
mailer.subject("Your code sucks"
mailer.body("details")
val result=mailer.send()
println(result)
- 위의 장황한 코드를 4가지 메소드를 사용해서 줄여보자.
apply를 이용한 반복참조 제거
val mailer =
Mailer()
.apply { from("builder@agiledeveloper.com") }
.apply { to("venkats@agiledeveloper.com") }
.apply { subject("Your code sucks") }
.apply { body("details") }
val result = mailer.send()
println(result)
- apply()를 사용해서 다중 호출을 사용할 수 있다.
val mailer = Mailer().apply {
from("builder@agiledeveloper.com")
to("venkats@agiledeveloper.com")
subject("Your code sucks")
body("details")
}
val result = mailer.send()
println(result)
- apply() 메소드는 apply()를 마지막으로 호출한 객체의 컨텍스트에서 람다를 실행시킨다.
- 그래서 apply()에 전달하는 람다에서 Mailer에 여러번의 메소드 호출을 사용할 수 있다.
run을 이용한 결과 얻기
val result = Mailer().run {
from("builder@agiledeveloper.com")
to("venkats@agiledeveloper.com")
subject("Your code sucks")
body("details")
send()
}
println(result)
- 메소드의 연속적인 호출 이후에 인스턴스를 더 이상 쓸 일이 없다면 run()을 사용할 수 있다.
- apply()와는 다르게 run() 메소드는 람다의 결과를 리턴해준다.
let을 이용해 객체를 아규먼트로 넘기기
fun createMailer() = Mailer()
fun prepareAndSend(mailer: Mailer) = mailer.run {
from("builder@agiledeveloper.com")
to("venkats@agiledeveloper.com")
subject("Your code suks")
body("details")
send()
}
val result = createMailer().let {
prepareAndSend(it)
}
println(result)
- 함수에서 받은 인스턴스를 다른 메소드의 아규먼트로 전달하고 싶다면 let() 메소드를 사용할 수 있다.
- createMailer()의 결과는 let()으로 전달되고, let()은 전달받은 파라미터를 다시 prepareAndSend() 메소드에 전달한다.
그리고 함수가 리턴한 것을 호출자에게 다시 리턴한다.
also를 사용한 void 함수 체이닝
fun createMailer() = Mailer()
fun prepareMailer(mailer: Mailer):Unit {
mailer.run {
from("builder@agiledeveloper.com")
to("venkats@agiledeveloper.com")
subject("Your code suks")
body("details")
}
}
fun sendMail(mailer: Mailer): Unit {
mailer.send()
println("Mail sent")
}
val mailer = createMailer()
prepareMailer(mailer)
sendMail(mailer)
createMailer()
.also(::prepareMailer)
.also(::sendMail)
- also()를 이용하면 함수를 호출할 때 체이닝을 사용할 수 있다. also()가 타깃 객체를 람다에 파라미터로 전달하고, 람다의 리턴을 무시한 후 타깃을 다시 호출한 곳으로 리턴하기 때문이다.
암시적 리시버
리시버 전달
var length = 100
val printIt: (Int) -> Unit = { n: Int ->
println("n is $n, length is $length")
}
printIt(6) //n is 6, length is 100
- 먼저 일반적인 람다 표현식을 살펴보자.
- 이 코드에서 람다 내부의 스코프에 length는 없다. 그래서 컴파일러는 렉시컬 스코프의 변수에서 length를 찾는다.
var length = 100
val printIt: String.(Int) -> Unit = { n: Int ->
println("n is $n, length is $length")
}
- 람다의 시그니처를 약간만 바꾸면 람다에 리시버를 세팅할 수 있다.
- 위 코드는 (Int) -> Unit 대신에 String.(Int) -> Unit을 사용했다.
- String.(Int) 문법은 람다가 String 인스턴스의 컨텍스트에서 실행된다는 의미를 가지고 있다.
- 변수의 스코프를 해결할 때 컴파일러는 리시버의 스코프를 먼저 확인하고, 찾을 수 없을 경우에 렉시컬 스코프를 찾게 된다.
printIt("Hello", 6) //n is 6, length is 5
- 리시버를 사용하는 람다를 호출할 때 추가적인 아규먼트를 전달해야한다. 위와 같이 리시버를 첫 번째 아규먼트로 전달하고, 실제 람다의 파라미터를 두 번째 아규먼트로 사용할 수 있다.
"Hello".printIt(6) //n is 6, length is 5 //n is 6, length is 5
- 하지만 코틀린에서는 람다를 리시버의 확장 함수처럼 사용하는 방식을 더 선호한다.
- 출력을 보면 렉시컬 스코프의 값이 아니라 리시버인 String의 length를 보여준다.
리시버를 이용한 멀티플 스코프
- 람다 표현식은 다른 람다 표현식에 중첩될 수 있다. 이때 내부의 람다 표현식은 멀티플 리시버를 가진 것처럼 보인다.
- 람다 하나는 하나의 리시버만 가질 수 있지만, 중첩 레벨에 따라서 변수에 바인딩하기 위해 멀티플 스코프를 가질 수 있다.
- 람다를 감싸고 있는게 클래스가 아니라 함수이기 때문에 this@OuterFunctionName 문법을 이용해서 외부 스코프를 참조한다.
fun top(func: String.() -> Unit) = "hello".func()
fun nested(func: Int.() -> Unit) = (-2).func()
top {
println("In outer lambda $this and $length")
nested {
println("in inner lambda $this and ${toDouble()}")
println("from inner through receiver of outer: ${length}")
println("from inner to outer receiver ${this@top}")
}
}
- top() 함수는 "hello"를 리시버로 사용하고, nested는 (-2)를 리시버로 사용한다.
- 중첩된 람다 표현식에서 toDouble()은 nested() 함수의 리시버인 Int (-2)에서 실행된다.
- 하지만 Int에는 length 속성이 없기 때문에 length() 함수는 부모의 리시버로 라우팅된다.
- 명시적으로 부모 리시버를 참조하기 위해서 this@top처럼 @ 문법을 사용한다.
728x90
'Kotlin > 다재다능 코틀린 프로그래밍' 카테고리의 다른 글
코틀린에서 구현하는 유창성 (1) (0) | 2023.10.13 |
---|---|
내부 반복과 지연 연산 (0) | 2023.09.22 |
람다를 사용한 함수형 프로그래밍(2) (0) | 2023.09.22 |
함수형 코틀린 (1) (0) | 2023.09.08 |
델리게이션을 통한 확장 (2) (0) | 2023.09.08 |
Comments