Simpler User Interfaces with Capability-Passing
Session Abstract
Representing user interfaces as effects in the capability-passing style gives a simple framework for creating UIs. In this talk I’ll explain how capability-passing works and use it to implement a simple terminal user interface library. We’ll then discuss what general principles we can take from this, that can apply to any problem.
Session Description
Description
Modern user interface libraries represent interfaces as effects in the capability-passing style. Scala 3 has excellent support for this style of programming. I’ll demonstrate this by implementing a simple terminal user interface framework in the capability-passing style. The resulting framework is straightforward to use, and catches certain classes of errors at compile-time. I’ll then describe the general principles we can take from this exercise, giving you the mental models to use capability-passing in your own projects.
Goals
The main goals of this talk are:
1. To show that capability-passing is a useful tool, by building a simple terminal user interface framework with it.
2. To abstract the design into general principles for creating frameworks using capability-passing.
Outline
- Effects, context, and capabilities. Explain three core ideas of effects, context, and capabilities. Describe the goals of an effect system: tracking effect usage and preventing effects when it is not safe to use them.
- Interfaces are hard. From a simple UI example, examine the underlying issues and see why they are difficult to implement. We’ll see we need to create two structures, the layout tree and the event graph, and these structures have mutual dependencies.
- Modern frameworks. Look at an example modern user interface framework (probably Jetpack Compose, as it has all the features I want to show) and see how it solves the problems. Show that this is capability-passing, but not made explicit.
- User Interfaces in Scala. Can we do better in Scala? Of course we can. Implement a basic terminal user interface framework in Scala. Show we can represent both the layout tree and event graph as capabilities, using just language features in Scala.
- Type systems squash bugs. Show how we can use the type system to prevent simple but annoying bugs, such as using an event source outside a reactive scope. Talk about how capture-checking can prevent more bugs (but I probably won’t have time to go into details.)
- Capability-passing as a design strategy. Abstract what we’ve learned. We can build tree structures, more flexibly than with algebraic data types, using capability-passing. We can also build graph structures if we reify graph vertices. Finally, we can do away with sequencing combinators such as
mapandflatMapand just run in “direct-style”. Show how this still retains desirable properties of reasoning and composition.