Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a DSL... In Kotlin

Building a DSL... In Kotlin

Kotlin is a multipurpose language that makes building domain-specific languages (DSLs) a breeze. But, what is a DSL? A domain-specific language is a small programming dictionary designed specifically to express solutions to problems focused on a particular aspect of a software system. There are a lot of them around, like CSS for styling, HTML for websites, and SQL for querying databases. A well-designed DSL is much easier to program with than a traditional library. This is just one of the nice things about DSLs. Let me show you how I built one in Kotlin.

In this session we will learn how to design a DSL and understand why we should use Functional Programming (FP) and Kotlin to build one. We will analyze Do’s and Don’ts from my experience creating a UI Automator-gesture generation DSL (https://github.com/Guardiola31337/uiagesturegen). Along with Kotlin's power and DSL building facilities, we will explore how we can make this DSL entirely stack safe, composable and reusable under different runtime requirements thanks to KΛTEGORY (https://github.com/kategory/kategory), a library which provides abstractions for FP in Kotlin.

By the end of the talk, you will be itching to write your own!

Pablo Guardiola

November 07, 2017
Tweet

More Decks by Pablo Guardiola

Other Decks in Programming

Transcript

  1. PABLO GUARDIOLA
    Building a DSL…
    In Kotlin

    View Slide

  2. PABLO GUARDIOLA
    Building a DSL…
    In Kotlin

    View Slide

  3. Building a DSL…
    In Kotlin
    With
    KΛTEGORY
    PABLO GUARDIOLA

    View Slide

  4. key concepts
    @Guardiola31337

    View Slide

  5. key concepts
    @Guardiola31337
    Composable

    View Slide

  6. key concepts
    @Guardiola31337
    Composable
    Extensible

    View Slide

  7. key concepts
    @Guardiola31337
    Composable
    Extensible
    Testable

    View Slide

  8. Composable
    Extensible
    Testable
    Integration w/ current frameworks
    key concepts
    @Guardiola31337

    View Slide

  9. dsl
    @Guardiola31337

    View Slide

  10. dsl (noun)
    “A computer programming language of limited
    expressiveness focused on a particular domain.”
    @Guardiola31337

    View Slide

  11. dsl
    @Guardiola31337
    Programming language

    View Slide

  12. dsl
    @Guardiola31337
    Programming language
    Language nature

    View Slide

  13. dsl
    @Guardiola31337
    Programming language
    Language nature
    Limited expressiveness

    View Slide

  14. Programming language
    Language nature
    Limited expressiveness
    Domain focus
    dsl
    @Guardiola31337

    View Slide

  15. uiagesturegen
    @Guardiola31337

    View Slide

  16. uiagesturegen
    KΛTEGORY
    @Guardiola31337

    View Slide

  17. usage
    @Guardiola31337

    View Slide

  18. usage
    private fun composing(element: UiObject): Boolean {
    val actions = pinchOut(100, 50)
    .swipeDown(30)
    .swipeRight(30)
    .pinchOut(100, 50)
    .swipeUp(100)
    return actions.validate(element).hasCompletedCorrectly()
    }
    @Guardiola31337

    View Slide

  19. @Guardiola31337

    View Slide

  20. free dsl
    @Guardiola31337

    View Slide

  21. free dsl
    @Guardiola31337
    Define your algebra (a.k.a. operations)

    View Slide

  22. algebra
    @higherkind sealed class GesturesDSL : GesturesDSLKind {
    data class WithView(val f: (UiObject) -> A) : GesturesDSL()
    object Click : GesturesDSL()
    object DoubleTap : GesturesDSL()
    data class PinchIn(val percent: Int, val steps: Int) : GesturesDSL()
    // ...
    companion object : FreeApplicativeApplicativeInstance
    }
    @Guardiola31337

    View Slide

  23. algebra
    @higherkind sealed class GesturesDSL : GesturesDSLKind {
    data class WithView(val f: (UiObject) -> A) : GesturesDSL()
    object Click : GesturesDSL()
    object DoubleTap : GesturesDSL()
    data class PinchIn(val percent: Int, val steps: Int) : GesturesDSL()
    // ...
    companion object : FreeApplicativeApplicativeInstance
    }
    @Guardiola31337

    View Slide

  24. algebra
    @higherkind sealed class GesturesDSL : GesturesDSLKind {
    data class WithView(val f: (UiObject) -> A) : GesturesDSL()
    object Click : GesturesDSL()
    object DoubleTap : GesturesDSL()
    data class PinchIn(val percent: Int, val steps: Int) : GesturesDSL()
    // ...
    companion object : FreeApplicativeApplicativeInstance
    }
    @Guardiola31337

    View Slide

  25. algebra
    @higherkind sealed class GesturesDSL : GesturesDSLKind {
    data class WithView(val f: (UiObject) -> A) : GesturesDSL()
    object Click : GesturesDSL()
    object DoubleTap : GesturesDSL()
    data class PinchIn(val percent: Int, val steps: Int) : GesturesDSL()
    // ...
    companion object : FreeApplicativeApplicativeInstance
    }
    @Guardiola31337

    View Slide

  26. free dsl
    @Guardiola31337
    Define your algebra (a.k.a. operations)
    Lift everything to the context of free

    View Slide

  27. lift
    fun withView(f: (UiObject) -> A): ActionDSL =
    FreeApplicative.liftF(GesturesDSL.WithView(f))
    // ...
    fun ActionDSL.click(): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.click())
    fun click(): ActionDSL = FreeApplicative.liftF(GesturesDSL.Click)
    // ...
    @Guardiola31337

    View Slide

  28. lift
    fun withView(f: (UiObject) -> A): ActionDSL =
    FreeApplicative.liftF(GesturesDSL.WithView(f))
    // ...
    fun ActionDSL.click(): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.click())
    fun click(): ActionDSL = FreeApplicative.liftF(GesturesDSL.Click)
    // ...
    @Guardiola31337

    View Slide

  29. lift
    fun ActionDSL.doubleTap(): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.doubleTap())
    fun doubleTap(): ActionDSL = FreeApplicative.liftF(GesturesDSL.DoubleTap)
    fun ActionDSL.pinchIn(percent: Int, steps: Int): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.pinchIn(percent, steps))
    fun pinchIn(percent: Int, steps: Int): ActionDSL =
    FreeApplicative.liftF(GesturesDSL.PinchOut(percent, steps))
    // ...
    @Guardiola31337

    View Slide

  30. lift
    fun ActionDSL.doubleTap(): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.doubleTap())
    fun doubleTap(): ActionDSL = FreeApplicative.liftF(GesturesDSL.DoubleTap)
    fun ActionDSL.pinchIn(percent: Int, steps: Int): ActionDSL =
    combine(this, com.pguardiola.uigesturegen.dsl.pinchIn(percent, steps))
    fun pinchIn(percent: Int, steps: Int): ActionDSL =
    FreeApplicative.liftF(GesturesDSL.PinchOut(percent, steps))
    // ...
    @Guardiola31337

    View Slide

  31. free dsl
    @Guardiola31337
    Define your algebra (a.k.a. operations)
    Lift everything to the context of free
    Provide semantics with the interpreter

    View Slide

  32. interpreter
    fun safeInterpreter(view: UiObject): FunctionK>> =
    object : FunctionK>> {
    override fun invoke(fa: GesturesDSLKind): ValidatedKind, A> {
    // ...
    return when (g) {
    is GesturesDSL.Click -> view.click().validate({ GestureError.ClickError(view) })
    // ...
    } as ValidatedKind, A>
    }
    }
    @Guardiola31337

    View Slide

  33. interpreter
    fun loggingInterpreter(view: UiObject): FunctionK> =
    object : FunctionK> {
    override fun invoke(fa: GesturesDSLKind): EitherKind {
    println(fa)
    return safeInterpreterEither(view) as EitherKind
    }
    }
    @Guardiola31337

    View Slide

  34. Define your algebra (a.k.a. operations)
    Lift everything to the context of free
    Provide semantics with the interpreter
    Execute the AST using the interpreter
    free dsl
    @Guardiola31337

    View Slide

  35. unfold
    fun ActionDSL.validate(view: UiObject): ValidatedNel =
    Try {
    this.foldMap(
    safeInterpreter(view),
    Validated.applicative>()).ev()
    }.fold({ GestureError.UnknownError(it).invalidNel() }, { it })
    @Guardiola31337

    View Slide

  36. unfold
    fun ValidatedNel.hasCompletedCorrectly(): Boolean =
    this.isValid
    fun ValidatedNel.errors(): List =
    this.fold({ it.all }, { emptyList() })
    @Guardiola31337

    View Slide

  37. unfold
    fun ValidatedNel.hasCompletedCorrectly(): Boolean =
    this.isValid
    fun ValidatedNel.errors(): List =
    this.fold({ it.all }, { emptyList() })
    @Guardiola31337

    View Slide

  38. usage
    private fun composingWithPlus(element: UiObject,
    pointers: List>): Boolean {
    val actions = pinchOut(75, 30) + swipeDown(30) + multiTouch(pointers)
    return actions.failFast(element).hasCompletedCorrectly()
    }
    @Guardiola31337

    View Slide

  39. @Guardiola31337

    View Slide

  40. takeaways
    @Guardiola31337

    View Slide

  41. takeaways
    @Guardiola31337
    Composable

    View Slide

  42. takeaways
    @Guardiola31337
    Composable
    Extensible

    View Slide

  43. takeaways
    @Guardiola31337
    Composable
    Extensible
    Reusable using different interpreters

    View Slide

  44. Composable
    Extensible
    Reusable using different interpreters
    Integration w/ your current stack
    takeaways
    @Guardiola31337

    View Slide

  45. thank you!
    PABLO GUARDIOLA

    View Slide

  46. questions?
    PABLO GUARDIOLA

    View Slide

  47. we’re hiring!
    mapbox.com/jobs
    PABLO GUARDIOLA

    View Slide

  48. kategory.io
    https://github.com/Guardiola31337/uiagesturegen
    https://github.com/Kotlin/KEEP/pull/87
    https://github.com/JorgeCastilloPrz/KotlinAndroidFunctional
    Domain-Specific Languages. Martin Fowler.
    Special shout out to Raúl Raja, Paco Estévez and Jorge Castillo
    references
    @Guardiola31337

    View Slide