계기

코틀린으로 코딩하는도중 예전에 쓰던 exposed 프레임워크의 코드줄에서 궁금증이 생겼는데.

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

object Users : Table() {
    ...
    val name = varchar("name", length = 50)
    ...
}
transaction {
    ....
    ....
    Users.update({ Users.name eq "city"}) {
        it[name] = "bob"
    }
}

이라는 코드줄이 있다고 할때, User 싱글톤 객체에 name에 infix 함수인 eq가 있다. 그럼 이걸 update함수 밖에서 써도 될까에서 시작해서 그렇게 작성해서 컴파일 해봤는데.

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

object Users : Table() {
    ...
    val name = varchar("name", length = 50)
    ...
}
transaction {
    ....
    ....
    val exist = Users.name eq "city"
    Users.update({ exist }) {
        it[name] = "bob"
    }
}


/*
e: main.kt: (13, 32): Unresolved reference: eq
*/

이러한 에러가 나왔다. 분명 update안에는 컴파일이 됬는데 밖에서 사용하면 해당 함수는 없다고 에러가 나온다. 그래서 찾아본 결과 코틀린의 확장함수랑 관련된걸 알게 됬다.

저 에러를 해결하는 법은 import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq 추가하면 된다.

본문 및 설명

우선 사람의 이름과 나이를 출력하는 프로그램을 프로그래밍 한다고 치자.

open class Person {
    internal var name : String = ""
    internal var age : Int = 0
}

단순하게 사람을 추상화한 클래스이다. 이것을 빌더패턴을 사용하는 방식으로 작성한다고 치자. 그래서 평소에 다른언어에서 하던 방식으로 작성하였다.

package com.example.kotlinTest.hello

class PersonBuilder(val person: Person) {

    fun name(n : String) :PersonBuilder {
        this.person.name = n
        return this
    }
    fun age(a : Int) : PersonBuilder {
        this.person.age = a
        return this
    }
    fun build() {
        print("hi my name is : ${person.name}\n")
        println("my age is : ${person.age}\n")
    }
}
import com.example.kotlinTest.hello.*

fun main() {
    var builder = PersonBuilder(Person())
    builder
        .name("ahn")
        .age(25)
        .build()
}

이렇게 작성하고보니 비록 본인의 코딩실력이 좋지않아도 이정도면 괜찮지 않을까 싶었지만, 사실 코틀린은 다른언어보다 지원하는 문법들이 많았고 이것을 활용하면 더 보기 좋은 코드를 작성할수 있다고 한다. 그래서 찾아보니 infix 함수랑 리시버 함수가 있다고 한다.

해당 함수들의 설명은 공식 문서에 가면 있으니 먼저 보고 오는게 좋다.
infix 함수 : https://kotlinlang.org/docs/functions.html#infix-notation
리시버 함수 : https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver

이것들을 활용하면 위의 update함수처럼 별도의 import가 없으면 안에서는 사용할수 있지만, 밖에서는 사용못하게 할수도 있다. 이제 infix,리시버들을 활용한 소스코드이다.

package com.example.kotlinTest.hello
/*
본래 해당 객체안의 확장 함수들은, PersonMethod를 임포트 안하면 사용이 안된다.
*/
object PersonMethod {
    infix fun Person.age(other : Int) {
        this.age = other
    }
    infix fun Person.name(other : String) {
        this.name = other
    }

}
/*
block 변수로 리시터 함수 리터럴을 받게 됨으로써, PersonMethod안의 함수들이 사용가능해 진다.
*/
fun build(person: Person,block : PersonMethod.(Person) -> Unit) {
    PersonMethod.block(person)
    print("hi my name is : ${person.name}\n")
    println("my age is : ${person.age}\n")
}
import com.example.kotlinTest.hello.build
import com.example.kotlinTest.hello.Person

fun main() {
    build(Person()) {
        it name "ahn"
        it age  25
    }
}

예시가 좋지 못하지만, 이렇게 소스코드를 작성할수가 있다. 즉 PersonMethod 객체를 임포트 안해도 Person 확장 함수를 사용할수 있다.

위 계기의 소스코드 해결법처럼 확장함수가 정의된 import com.example.kotlinTest.hello.PersonMethod를  추가하면 밖에서도 사용이 가능하다.