All Projects → eviefp → purescript-halogen-example

eviefp / purescript-halogen-example

Licence: other
Sample halogen app that uses a few DSLs within the application's free monad

Programming Languages

purescript
368 projects

Projects that are alternatives of or similar to purescript-halogen-example

factorio-example-mod
Lightweight modular example mod with various features and compatibilities
Stars: ✭ 15 (-75.81%)
Mutual labels:  example
ionic-truncated-slider-cards
Customized slider component to achieve a fancy horizontal truncated slider, basically for short list of cards
Stars: ✭ 19 (-69.35%)
Mutual labels:  example
fastify-example
Example webapp with Fastify
Stars: ✭ 18 (-70.97%)
Mutual labels:  example
scala-basic-skeleton
Starting point if you want to bootstrap a project in Scala
Stars: ✭ 16 (-74.19%)
Mutual labels:  example
bKash-payment-gateway-web-demo
bKash payment gateway API integration documentation for web.
Stars: ✭ 74 (+19.35%)
Mutual labels:  example
ifconfig.top
Source code of ifconfig.top website
Stars: ✭ 19 (-69.35%)
Mutual labels:  example
hunt-skeleton
hunt framework project template.
Stars: ✭ 20 (-67.74%)
Mutual labels:  example
backbone.marionette.example
MVVM-flavored example CRUD app with Backbone.Marionette + Backbone.stickit + Browserify
Stars: ✭ 37 (-40.32%)
Mutual labels:  example
NodeExpressCRUD
Node, Express, Mongoose and MongoDB CRUD Web Application
Stars: ✭ 45 (-27.42%)
Mutual labels:  example
java-basic-skeleton
☕🚀 Java Bootstrap: Skeleton for your new projects
Stars: ✭ 37 (-40.32%)
Mutual labels:  example
HTML-Crypto-Currency-Chart-Snippets
💹 Simple HTML Snippets to create Tickers / Charts of Cryptocurrencies with the TradingView API 💹
Stars: ✭ 89 (+43.55%)
Mutual labels:  example
flask-nginx-tutorial
Basic connection of flask and nginx via docker-compose
Stars: ✭ 24 (-61.29%)
Mutual labels:  example
flutter-animations
medium.com/flutter-jp/implicit-animation-b9d4b7358c28
Stars: ✭ 37 (-40.32%)
Mutual labels:  example
line-fido2-server
FIDO2(WebAuthn) server officially certified by FIDO Alliance and Relying Party examples.
Stars: ✭ 350 (+464.52%)
Mutual labels:  example
example-echo-server
🔗 An example Gleam web application
Stars: ✭ 59 (-4.84%)
Mutual labels:  example
erws
Erlang Websockets example using Cowboy
Stars: ✭ 43 (-30.65%)
Mutual labels:  example
Discord.Net-Example
Discord.Net Example bots
Stars: ✭ 104 (+67.74%)
Mutual labels:  example
go-12factor-example
Example the 12factor app using golang
Stars: ✭ 20 (-67.74%)
Mutual labels:  example
rocket-yew-starter-pack
Example boilerplate for websites in pure Rust
Stars: ✭ 77 (+24.19%)
Mutual labels:  example
guide vue-cli-3-multiple-entry-points
Simple guide to show how to create multiple entry points (pages) using vue-cli-3
Stars: ✭ 29 (-53.23%)
Mutual labels:  example

Purescript Halogen Example

Sample Halogen app with a few DSLs implemented as the application's reader monad.

The overview presented here has some simplified examples. You will find some of the types presented here have less arguments / constructors / etc. than the actual types used in the source code.

You can check out the result here.

Purpose

This repository aims to be an example of how to use an application monad with Halogen in order to create a DSL that can be used inside the component's eval.

In this repository, you'll find examples for DLS's that allow you to:

  • read some static environment (through MonadAsk)
  • navigate to different routes, along with a routing component
  • read or modify the global state of the application
  • run queries on a (dummy) server API
  • trigger the root component to show a dialog box and execute commands depending on action taken

Our application's DSLs

Each DSL is expressed as a typeclass. These DSLs can define any number of functions, each of which should return a monadic result. For example, our Navigation DSL is defined as:

class Monad m <= NavigationDSL m where
  navigate :: Route -> m Unit

The navigate function will most likely incur a side-effect, so we express that by returning m Unit.

In order to be able to use these DSLs in Halogen, we need to lift these operations to its monad, which is HalogenM:

instance navigationDSLHalogenM :: NavigationDSL m => NavigationDSL (HalogenM s f g p o m) where
  navigate = lift <<< navigate

What this basically says is that whenever you use navigate within a HalogenM context, we will lift the DSL to the inner-monad m, which means we'll need to have an instance ourselves.

The Reader Pattern

We will be using the _ReaderT pattern. If you have not used ReaderT before, please go through The ReaderT design pattern post.

The env type we are using is

type Environment =
  { token :: APIToken
  , push :: PushType -> Effect Unit
  , answer :: Int
  , state :: Ref GlobalState
  }

These are needed for all the various DSLs we are using. Specifically, the ServerDSL uses the token, NavigationDSL and DialogDSL use the push field to request a route change or displaying the dialog box, and the global StateDSL uses state.

Our application's monad

We can define our monad as:

newtype ExampleM a = ExampleM (ReaderT Environment Aff a)

We can now derive instances for Functor, Apply, Applicative, Bind, Monad, MonadEffect, and MonadAff for free.

We also need the NavigationDSL instance for ExampleM:

instance navigationDSLExampleM :: NavigationDSL ExampleM where
  navigate route = ExampleM do
    env <- ask
    liftEffect $ env.push $ PushRoute route

We basically use our environment's push to send the new route to the event listener.

Natural transform run

Halogen needs to know how to effectively run our monad, which is expressed by its hoist function:

hoist
  :: forall h f i o m m'
   . Bifunctor h
  => Functor m'
  => (m ~> m')
  -> Component h f i o m
  -> Component h f i o m'

What this does, basically, is given a component that runs under monad m and a way to go from m to m' (through the natural transform m ~> m'), then we can construct the component that runs under monad m'.

It's also worth noting that runUI assumes a component that runs under the Aff monad, so that means m' needs to be Aff. And since m is our own monad, ExampleM, it follows we will write:

runExampleM :: forall a. ExampleM a -> Environment -> Aff a
runExampleM m env = runReaderT (unwrap m) env

All that remains is somehow figure out how to do the route change through an Eff or Aff. Which brings us to...

Signaling back to main

Some of our DSLs might need to signal back to main. The basic idea is we create a new event in main and we pass the function that can push to this event in the environment to our runExampleM transform. This means we'll have a way of sending messages to our main.

Back in main, we'll have to handle them somehow. And since we also create the root component there, we could use its driver's query to send actions to that component.

Back in main:

main = HA.runHalogenAff do
  body  <- HA.awaitBody
  state <- liftEffect $ Ref.new 0
  event <- liftEffect create
  let environment =
        { token: APIToken secretKey
        , answer: 42
        , state
        , push: event.push
        }

  let router' = H.hoist (flip runExampleM environment) R.component
  driver <- runUI router' unit body
  liftEffect $ subscribe event.event (handler driver)

We omitted the definition for handler for brevity. You can check the Main.purs file for details. The main idea is we create the environment, which has everything ExampleM needs to run everything we care about.

The handler function sends messages to the router (the main Halogen component), depending on its input (one message is for changing the current route, and the other is for showing a dialog box).

MonadAsk

MonadAsk allows us to get the current environment. This is initialized in main and passed down to our runExampleM function.

StateDSL

Unfortunately, we can't use MonadState because HalogenM already has an instance for it for each component's state. We need to define our own state monad, and one way is presented in the StateDSL class.

ServerAPI

We have a dummy API method in the Example.Server.ServerAPI module, but it could be a function that does an Ajax request just as well. We assume our API needs a token, but our DSL does not ask for it.

ShowDialog

This is an example where we pass actions as our ExampleM monad. Basically, we want our root component to show a dialog with a custom set of buttons. Each of these buttons has an action that will run under ExampleM, which means it can run all the DSLs that we define.

The way this works is we initially send these actions as the dialog options, under any monad m. The ExampleM instance for DialogDSL assumes both the monad that it runs under and the monad used to run the actions is ExampleM. It transforms the options to Aff and pushes them to the router through the handler stored in the environment.

Parallel

@thomashoneyman kindly contributed a version which enables parallel computations to the application's monad. You can check it out in this PR.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].