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. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. free dsl @Guardiola31337 Define your algebra (a.k.a. operations) Lift everything

    to the context of free Provide semantics with the interpreter
  11. 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
  12. 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
  13. 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
  14. 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
  15. unfold fun <A> ValidatedNel<GestureError, A>.hasCompletedCorrectly(): Boolean = this.isValid fun <A>

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

    ValidatedNel<GestureError, A>.errors(): List<GestureError> = this.fold({ it.all }, { emptyList() }) @Guardiola31337
  17. 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