Understanding Free Monad Queries

Most of the time Halogen queries look like this:

1
2
3
4
data QueryF ( other type arguments omitted ) a
  = ...
  | SetVisibility Visibility a
  | GetVisibility (Visibility -> a)

(where QueryF is used directly as the Halogen query functor)

This library takes a slightly different approach: the query functor is actually Control.Monad.Free.Free QueryF, the free monad generated by the query functor.

This allows queries the full power of monadic (and applicative) composition: sequencing effects, determining control flow based on previous results, and my favorite: doing nothing (pure unit).

We now define smart query constructors for this Free pattern like so:

1
2
3
4
5
6
7
8
-- | Set the container visibility (`On` or `Off`).
setVisibility ::  o item eff. Visibility -> Query o item eff Unit
setVisibility v = liftF (SetVisibility v unit)

-- | Get the container visibility (`On` or `Off`). Most useful when sequenced
-- | with other actions.
getVisibility ::  o item eff. Query o item eff Visibility
getVisibility = liftF (GetVisibility id)

Different patterns

In the simple cases, the helpers Halogen use to work with raw query constructors are folded into the smart Free query constructors already: H.action (SetVisibility On) becomes simply setVisiblity On, and similarly H.request GetVisibility is just getVisibility. This is because these patterns are typically present already smart constructors: setVisibility returns Free QueryF Unit, since it is an action, and getVisibility returns Free QueryF Visibility, since it requests the visibility. This allows for easy composition in do notation:

1
2
3
toggleVisibility = do
  vis <- getVisibility
  setVisibility (not vis)

C’est très facile!

Event handlers look a little different. This is one example:

1
2
3
4
HE.onMouseDown \ev -> Just do
    Select.preventClick ev
    Select.select index
    when doBlur Select.triggerBlur

(Of course you may return Nothing if you so wish, but its effect is just like pure unit now.)

If you do not need access to the argument ev, Select.always provides a simple shortcut for const <<< Just:

1
HE.onMouseOver $ Select.always $ Select.highlight (Index index)

Returning non-unit values

Use map or <$ or pure to return other types of values from a query. So, instead of something like this:

1
2
H.subscribe $ eventSource' someEventSource
  \value -> Just (SetVisibility value H.Listening)

Use

1
2
H.subscribe $ eventSource' someEventSource
  \value -> Just $ setVisibility value $> H.Listening

or

1
2
3
4
H.subscribe $ eventSource' someEventSource
  \value -> Just do
    setVisibility value
    pure H.Listening

Many thanks to Nicholas Scheel for providing the implementation of QueryF and the documentation above.