Home Games Art Blog Work ...

Surprising use of koka effects for frontends

by Janek

In the past few days I experimented with the wonderful koka programming language. I found out about the vast amount of compile time targets, one of them being javascript. Naturally my first instinct was to implement the elm architecture (TEA).

This ended up being a fun exercise and an exploration. The elm “counter” example, implemented in koka with a virtual dom and TEA runtime, looks like this

import vdom
import html
import events

pub fun main() : io ()
  run-app(init(), update, view, "#app")

type msg
  Increase
  Decrease

alias model = int

fun init()
  0

fun update( msg : msg, model : model ) : model
  match msg
    Increase -> model + 1
    Decrease -> model - 1

fun view( model : model ) : node<msg>
  div([], [
    button([Decrease.on-click], ["-".node]),
    model.show.node,
    button([Increase.on-click], ["+".node])
  ])

Take a look at github.com/janekx21/koka-vdom for more examples.

You have your standard init, view, update functions along with your model and msg datatypes. If you dipped your toe just a little into the elm world, this should look very familiar.

One koka feature that I wanted to try was the effect system, and more specifically the “val” effects. They can be written like this:

effect theme-color
  val background-color : string
  val foreground-color : string
  val primary-color : string

This defines a new effect called theme-color with three values, in this case colors for a theme. Using these in your components is easy. Just reference them:

fun line-input(attr)
  Elem("input", [
    Style("background-color", background-color),
    Style("border", "2px solid " ++ primary-color),
    Style("border-radius", "16px"),
    Style("color", foreground-color),
    Style("padding", "4px 8px")
  ] ++ attr, [])

This function now has an effect type of theme-color. This fact is inferred from the referenced values. If you would now try to call that function, it would not work. You would get a type error “effects do not match”:

type error: effects do not match
  inferred effect: <theme-color|_e>
  expected effect: total

Your code needs to handle the theme-color effect using a handler or “with” block.

with
  val background-color = "white"
  val foreground-color = "black"
  val primary-color = "oklch(45% 0.24 277.023)"

After registering this handler, all functions that get called afterwards use the theme colors you put into scope. After getting this to work, I extended the idea to also have a theme type like this:

type theme
  Light
  Dark
  Retro

Using this type, I can handle this effect with a helper function:

fun theme/handle-color(theme, action)
  with
   val background-color = match theme
     Light -> "white"
     Dark -> "black"
     Retro -> "oklch(88.272% 0.049 91.774)"
   val foreground-color = match theme
     Light -> "black"
     Dark -> "white"
     Retro -> "oklch(41% 0.112 45.904)"
   val primary-color =  match theme
     Light -> "oklch(45% 0.24 277.023)"
     Dark -> "oklch(58% 0.233 277.117)"
     Retro -> "oklch(80% 0.114 19.571)"

  action()

The action has type () -> <theme-color|e> a, but theme/handle-color returns a e a. This means that this function handles the effect of the function using the theme argument. This reduces to a nice with clause:

with theme.handle-color

This next example uses this to style the whole application and the buttons individually.

Of course this could also be done with just pure functions. But in that case the theme-color effect would be another parameter that would introduce prop drilling and boilerplate.

fun main() : ...
fun a(..., theme-color) : ...
fun b(..., theme-color) : ...
fun c(..., theme-color) : ...
fun d(..., theme-color) : ...
fun e(..., theme-color) : ...
fun f(..., theme-color) : ...
fun line-input(..., theme-color) : ...

Or it would end up like elm-ui-with-context, bringing a context into the node type.

Impressum Datenschutzerklärung