the-dr-lazy / purescript-monarch

A simple but powerful PureScript library for declaring pure UIs
Mozilla Public License 2.0
9 stars 0 forks source link

Functor instance for `VirtualDomTree` type #21

Closed the-dr-lazy closed 3 years ago

the-dr-lazy commented 3 years ago

Motivation

In a real world application there are hundreds of messages that we would like our application respond to. It's like a nightmare to put all of them inside a single sum type!

data Message
  = UserClickedIncreaseButton
  | UserClickedDecreaseButton
  | ... -- 2000 sum types later (with spongebob squarepants voice)

So it's practical to break the Message type into smaller and modular types which will aggregate over a single Message type.

module Main where

data Message
  = GotHomeMessage Home.Message
  | GotLoginMessage Login.Message
  | GotPreferencesMessage Preferences.Message

view :: Html Message

module Page.Home where

data Message
  = UserClickedLoadMoreFeedButton
  | ...

view :: Html Message

module Page.Login where

data Message
  = UserChangedEmailInput String
  | UserChangedPasswordInput String
  | ...

view :: Html Message

module Page.Preferences where

data Message
  = UserToggledNewsletterSwitch boolean
  | ...

view :: Html Message

The huge Message sum type issue will resolve but an issue with view functions in each module begins. The view functions doesn't compose with each other.

module Main where

view :: Html Message
view =
  div_ [ Home.view -- `Html Home.Message` doesn't match with type `Html Message`
       , Login.view -- `Html Login.Message` doesn't match with type `Html Message`
       , Preferences.view -- `Html Preferences.Message` doesn't match with type `Html Message`
       ]

Summary

There should be a way to transform VirtualDomTree slots a into VirtualDomTree slots b. If we watch it from the following perspective:

f   ::                          VirtualDomTree slots a -> VirtualDomTree slots b
map :: Functor f => (a -> b) -> f                    a -> f                    b

There become possible to have a Functor instance for VirtualDomTree slots.

Basic Example

module Main where

view :: Html Message
view =
  div_ [ Home.view <#> GotHomeMessage
       , Login.view <#> GotLoginMessage
       , Preferences.view <#> GotPreferencesMessage
       ]

Implementation Details

There is a Tagger sum type in VirtualDomTree type.

https://github.com/the-dr-lazy/purescript-monarch/blob/6bb90e6877bd71cdd24ea6bca7654b753b62d0a1/src/Monarch/VirtualDom/VirtualDomTree.ts#L79-L84

So it's basically the same Tagger in the elm/virutal-dom. Functor's map just wraps the whole virtual DOM tree into a Tagger.

x :: VirtualDomTree slots a
f :: a -> b

fmap f x :: VirtualDomTree slots b
-- {
--    tag: Tagger,
--    f: f,
--    vNode: x
-- }

Based on the Tagger nodes in the virtual DOM tree, the OutputHandlersList will be created and it's state saves to the DOM.

Flags