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!

3c2bdd16c0ea8511dc254b8497a06f78?s=128

Pablo Guardiola

November 07, 2017
Tweet

Transcript

  1. PABLO GUARDIOLA Building a DSL… In Kotlin

  2. PABLO GUARDIOLA Building a DSL… In Kotlin

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

  4. key concepts @Guardiola31337

  5. key concepts @Guardiola31337 Composable

  6. key concepts @Guardiola31337 Composable Extensible

  7. key concepts @Guardiola31337 Composable Extensible Testable

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

  9. dsl @Guardiola31337

  10. dsl (noun) “A computer programming language of limited expressiveness focused

    on a particular domain.” @Guardiola31337
  11. dsl @Guardiola31337 Programming language

  12. dsl @Guardiola31337 Programming language Language nature

  13. dsl @Guardiola31337 Programming language Language nature Limited expressiveness

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

  15. uiagesturegen @Guardiola31337

  16. uiagesturegen KΛTEGORY @Guardiola31337

  17. usage @Guardiola31337

  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
  19. @Guardiola31337

  20. free dsl @Guardiola31337

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

  22. algebra @higherkind sealed class GesturesDSL<A> : GesturesDSLKind<A> { data class

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

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

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

    WithView<A>(val f: (UiObject) -> A) : GesturesDSL<A>() object Click : GesturesDSL<Unit>() object DoubleTap : GesturesDSL<Unit>() data class PinchIn(val percent: Int, val steps: Int) : GesturesDSL<Unit>() // ... companion object : FreeApplicativeApplicativeInstance<GesturesDSLHK> } @Guardiola31337
  26. free dsl @Guardiola31337 Define your algebra (a.k.a. operations) Lift everything

    to the context of free
  27. lift fun <A> withView(f: (UiObject) -> A): ActionDSL<A> = FreeApplicative.liftF(GesturesDSL.WithView(f))

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

    // ... fun ActionDSL<Unit>.click(): ActionDSL<Unit> = combine(this, com.pguardiola.uigesturegen.dsl.click()) fun click(): ActionDSL<Unit> = FreeApplicative.liftF(GesturesDSL.Click) // ... @Guardiola31337
  29. lift fun ActionDSL<Unit>.doubleTap(): ActionDSL<Unit> = combine(this, com.pguardiola.uigesturegen.dsl.doubleTap()) fun doubleTap(): ActionDSL<Unit>

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

    = FreeApplicative.liftF(GesturesDSL.DoubleTap) fun ActionDSL<Unit>.pinchIn(percent: Int, steps: Int): ActionDSL<Unit> = combine(this, com.pguardiola.uigesturegen.dsl.pinchIn(percent, steps)) fun pinchIn(percent: Int, steps: Int): ActionDSL<Unit> = FreeApplicative.liftF(GesturesDSL.PinchOut(percent, steps)) // ... @Guardiola31337
  31. free dsl @Guardiola31337 Define your algebra (a.k.a. operations) Lift everything

    to the context of free Provide semantics with the interpreter
  32. interpreter fun safeInterpreter(view: UiObject): FunctionK<GesturesDSLHK, ValidatedKindPartial<NonEmptyList<GestureError>>> = object : FunctionK<GesturesDSLHK,

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

    EitherKindPartial<GestureError>> { override fun <A> invoke(fa: GesturesDSLKind<A>): EitherKind<GestureError, A> { println(fa) return safeInterpreterEither(view) as EitherKind<GestureError, A> } } @Guardiola31337
  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
  35. unfold fun <A> ActionDSL<A>.validate(view: UiObject): ValidatedNel<GestureError, A> = Try {

    this.foldMap( safeInterpreter(view), Validated.applicative<NonEmptyList<GestureError>>()).ev() }.fold({ GestureError.UnknownError(it).invalidNel() }, { it }) @Guardiola31337
  36. unfold fun <A> ValidatedNel<GestureError, A>.hasCompletedCorrectly(): Boolean = this.isValid fun <A>

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

    ValidatedNel<GestureError, A>.errors(): List<GestureError> = this.fold({ it.all }, { emptyList() }) @Guardiola31337
  38. usage private fun composingWithPlus(element: UiObject, pointers: List<Array<MotionEvent.PointerCoords>>): Boolean { val

    actions = pinchOut(75, 30) + swipeDown(30) + multiTouch(pointers) return actions.failFast(element).hasCompletedCorrectly() } @Guardiola31337
  39. @Guardiola31337

  40. takeaways @Guardiola31337

  41. takeaways @Guardiola31337 Composable

  42. takeaways @Guardiola31337 Composable Extensible

  43. takeaways @Guardiola31337 Composable Extensible Reusable using different interpreters

  44. Composable Extensible Reusable using different interpreters Integration w/ your current

    stack takeaways @Guardiola31337
  45. thank you! PABLO GUARDIOLA

  46. questions? PABLO GUARDIOLA

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

  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