Slide 1

Slide 1 text

From Declarative UIs to Comonadic UIs CT Wu FEDC 2018 wu_ct wuct

Slide 2

Slide 2 text

Declarative UIs Abstract Out Imperative Detail const render = ({ showBanana, showApple }) !=> { if (showBanana) { $('.banana').show() } else { $('.banana').hide() } if (showApple) { $('.apple').show() } else { $('.apple').hide() } } const render = ({ showBanana, showApple }) !=>
{ showBanana !&&
} { showApple !&&
} !

Slide 3

Slide 3 text

• How do we compare different declarative UI approaches? • How do we discover a new one? • Is there a way to “learn once, apply everywhere”? • Is there an universal approach?

Slide 4

Slide 4 text

An abstraction over all declarative UI approaches

Slide 5

Slide 5 text

Declarative Imperative abstract abstract

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Declarative Imperative Comonadic abstract abstract

Slide 8

Slide 8 text

Declarative UIs are about “what to render” { showBanana: true, showApple: true } { showBanana: false, showApple: false }

Slide 9

Slide 9 text

But how do we move among states? { showBanana: true, showApple: true } { showBanana: false, showApple: false }

Slide 10

Slide 10 text

Comonads as Spaces

Slide 11

Slide 11 text

A State of an UI is a Point

Slide 12

Slide 12 text

A Comonad is a Space of All Possible Points

Slide 13

Slide 13 text

An User Action is a Movement Inside This Space

Slide 14

Slide 14 text

Different Spaces are Different Declarative UI Approaches!

Slide 15

Slide 15 text

Let’s See Some (Simplified) Code!

Slide 16

Slide 16 text

Type Signature Notation in PureScript “::” is for type annotations "PureScript" !:: String New types can be created via type constructors ["PureScript"] !:: Array String ![["Pure"], ["Script"!]] !:: Array (Array String)

Slide 17

Slide 17 text

Type Signature Notation in PureScript → is an infix type constructor for functions String → Number A function with multiple → is a curried function String → String → String

Slide 18

Slide 18 text

Type Signature Notation in PureScript ∀ (forall) is the universal quantifier and lowercase letters stand for type variables ∀ a. a → a 㱺 expresses constraints on type variables ∀ a. Monoid a ⇒ a !-> a !-> a

Slide 19

Slide 19 text

Type Signature Notation in PureScript We can also add constraints to type constructors ∀ f a. Functor f ⇒ f a “class” is for typeclasses class Functor f where map !:: ∀ a b. (a !-> b) !-> f a !-> f b

Slide 20

Slide 20 text

Type Signature Notation in PureScript “data” is for types data Tuple a b = Tuple a b data Either a b = Left a | Right b “instance” is for typeclass instances instance Functor (Tuple a) where map f (Tuple a b) = Tuple a (f b) “type” is for type alias type Name = String type Audience = Array Name

Slide 21

Slide 21 text

(Simplified) Comonad class Comonad w where extract !:: ∀ a. w a → a duplicate !:: ∀ a. w a → w (w a) extract duplicate

Slide 22

Slide 22 text

A Comonad is a Dual of a Monad extract :: ∀ w a. w a ! a pure :: ∀ m a. a ! m a extend :: ∀ w a b. (w a ! b) ! w a ! w b bindFlipped :: ∀ m a b. (a ! m b) ! m a ! m b duplicate :: ∀ w a. w a ! w (w a) join :: ∀ m a. m (m a) ! m a

Slide 23

Slide 23 text

Pairing is Just a Type Alias for A Higher Order Function type Pairing f g = ∀ a b c. (a → b → c) → f a → g b → c

Slide 24

Slide 24 text

Move in a Comonadic Space by Pairing move !:: ∀ w m a b . Comonad w !=> Pairing m w → m a → w b → w b move pairing movement space = pairing (\_ newspace → newspace) movement (duplicate space) pairing !:: ∀ a b c. (a → b → c) → f a → g b → c duplicate !:: ∀ w a. w a → w (w a)

Slide 25

Slide 25 text

Comonadic User Interfaces a is the type describing the user interface (ex: JSX) m is the functor to move in the comonad space (m Unit → Effect Unit) is the handler function (ex: onClick) type UI m a = (m Unit → Effect Unit) → a type Component w m a = w (UI m a)

Slide 26

Slide 26 text

Explore Comonadic Spaces To explore a comonad component means: 1. Initialize the UI with the initial state 2. Waiting for user actions which updating the state 3. Update the UI accordingly explore !:: ∀ w m a base . Comonad w !=> Component w m a → base Unit

Slide 27

Slide 27 text

Traced Pairs With Writer data Writer a b = Writer (Tuple a b) data Traced a b = Traced (a → b) instance (Monoid a) !=> Comonad (Traced a) where extract !:: ∀ b. Traced a b → b extract (Traced f) = f mempty duplicate !:: ∀ b. Traced a b → Traced a (Traced a b) duplicate (Traced f) = Traced \t → (Traced \t' → f (t !<> t'))

Slide 28

Slide 28 text

Writer Traced Pairing type Pairing f g = ∀ a b c. (a → b → c) → f a → g b → c writerTraced !:: ∀ a. Pairing (Writer a) (Traced a) writerTraced f (Writer (Tuple a w)) (Traced wb) = let b = wb w in f a b

Slide 29

Slide 29 text

Use purescript-react-basic as an Example explore !:: ∀ w m . Comonad w !=> Component w m → ReactComponent {} explore component = react { initialState: { space: component } , render: \props state setState !-> let dispatch movement = setState \s !-> { space: move writerTraced movement s.space } in extract state.space dispatch }

Slide 30

Slide 30 text

Our First Comonadic UI Component tracedComponent !:: Component (Traced (Additive Int)) (Writer (Additive Int)) tracedComponent = Traced render where render !:: Additive Int → UI (Writer (Additive Int)) render (Additive count) dispatch = R.button { onClick: dispatch (Writer (Tuple (Additive 1) Unit)) , children: [ R.text ("Increment: " !<> show count) ] } tracedReactComponent !:: ReactComponent {} tracedReactComponent = explore writerTraced tracedComponent

Slide 31

Slide 31 text

A More General Case data State s a = State (s !-> Tuple a s) data Store s a = Store (Tuple (s !-> a) s) stateStore !:: ∀ s. Pairing (State s) (Store s) stateStore f (State sas) (Store (Tuple sb s)) = let Tuple a s' = sas s in f a (sb s')

Slide 32

Slide 32 text

Explore Any Comonad With a Pairing explore !:: ∀ w m . Comonad w !=> Pairing m w !-> Component w m → ReactComponent {} explore pairing component = react { initialState: { space: component } , render: \props state setState !-> let dispatch movement = setState \s !-> { space: move pairing movement s.space } in extract state.space dispatch }

Slide 33

Slide 33 text

Store Comonad Component storeComponent !:: Component (Store Int) (State Int) storeComponent = Store (Tuple render 0) where render !:: Int !-> UI (State Int) render count dispatch = R.button { onClick: dispatch (State (\s !-> Tuple (s + 1) Unit) , children: [ R.text ("Increment: " !<> show count) ] } storeReactComponent !:: ReactComponent {} storeReactComponent = explore stateStore storeComponent

Slide 34

Slide 34 text

Other Examples • The Moore comonad is similar to Elm/Redux/ReasonReact. • The Cofree comonad is similar to Halogen. • The Sum comonad is for branching.

Slide 35

Slide 35 text

Q & A • How can this thing be useful? • It’s fun to know it! • Study existing declarative UI approaches. • Find new declarative UI approaches. • How to compose components? • Use monad transformers and comonad transformers. • Can I use it in JavaScript? • Yes, all needed algebras are in Fantasy Land Specification. • But it’s hard to find a monad-comonad pairing, isn’t it? • Cofree gives comonads from functors for free, and Co gives monads paired with given comonads for free!

Slide 36

Slide 36 text

References • Freeman, P., 2017, Declarative UIs Are The Future - And the Future Is Comonadic!, https://github.com/paf31/the-future-is-comonadic/blob/ master/docs/main.pdf • Xavier, A., 2017, Comonads for user interfaces, https://github.com/ arthurxavierx/purescript-comonad-ui-todos/blob/master/ ComonadsForUIs.pdf • The code in this slide https://github.com/wuct/purescript-comonad-ui- examples

Slide 37

Slide 37 text

Learn once, apply everywhere. wu_ct wuct