Skip to content

Instantly share code, notes, and snippets.

@nosix
Created September 7, 2017 04:23
Show Gist options
  • Save nosix/ec54dd2fe6822ada3b4e9100e18efa11 to your computer and use it in GitHub Desktop.
Save nosix/ec54dd2fe6822ada3b4e9100e18efa11 to your computer and use it in GitHub Desktop.
How to limit the scope of extension functions in Kotlin.
interface Extension
// Why is keyword `class` instead of `object`?
// Because we can use the `foo` function after importing `FooExtension.foo`.
class FooExtension : Extension {
fun String.foo(): String = "Foo${this}"
}
class BarExtension : Extension {
fun String.bar(): String = "Bar${this}"
}
fun <E : Extension> extendWith(extension: E, block: E.() -> Unit) = block.invoke(extension)
fun main(args: Array<String>) {
val name = "Buz Bar"
// println(name.foo())
// println(name.bar())
extendWith(FooExtension()) {
println(name.foo())
// println(name.bar())
extendWith(BarExtension()) {
println(name.foo())
println(name.bar())
}
}
}
@Alex100
Copy link

Alex100 commented Oct 17, 2018

How about this solution:

interface Extension
operator fun <E: Extension> E.invoke(body: E.() -> Unit) = Unit

object FooExtension : Extension {
    fun String.foo(): String = "Foo${this}"
}

object BarExtension : Extension {
    fun String.bar(): String = "Bar${this}"
}

fun main(args: Array<String>) {
    val name = "Buz Bar"

//    println(name.foo())
//    println(name.bar())

    FooExtension {
        println(name.foo())
//        println(name.bar())

        BarExtension {
            println(name.foo())
            println(name.bar())
        }
    }
}

@y4n9b0
Copy link

y4n9b0 commented Jan 6, 2022

@Alex100 , good idea, but with a little bug

interface Extension
operator fun <E: Extension> E.invoke(block: E.() -> Unit) = block() // block won't be executed if we don't invoke it

object FooExtension : Extension {
    fun String.foo(): String = "Foo${this}"
}

object BarExtension : Extension {
    fun String.bar(): String = "Bar${this}"
}

fun main(args: Array<String>) {
    val name = "Buz Bar"

//    println(name.foo())
//    println(name.bar())

    FooExtension {
        println(name.foo())
//        println(name.bar())

        BarExtension {
            println(name.foo())
            println(name.bar())
        }
    }
}

Another way:

// Why is keyword `class` instead of `object`?
// Because we can use the `foo` function after importing `FooExtension.foo`.
class FooExtension(block: FooExtension.() -> Unit) {
    init { block() }

    fun String.foo(): String = "Foo${this}"
}

class BarExtension(block: BarExtension.() -> Unit) {
    init { block() }

    fun String.bar(): String = "Bar${this}"
}

fun main(args: Array<String>) {
    val name = "Buz Bar"

//    println(name.foo())
//    println(name.bar())

    FooExtension {

        println(name.foo())
//        println(name.bar())

        BarExtension {
            println(name.foo())
            println(name.bar())
        }
    }
}

object can't stop using the foo function after importing FooExtension.foo, class will generate duplicated instance.
Finally, I give up this way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment