Skip to content

Suggestions for Reader integration on top of the effects api #15

@mlegenhausen

Description

@mlegenhausen

I would like to make all dependencies that are involed in side effects to be exchangable. It seems that the most common solution for this problem is the usage of a Reader (my first time using it).

What would be the best way to integrate it in elm-ts? I already experiment a little bit with it and this is what I came up with. I needed to write some code around the elm-ts functions to get it working.

import { empty } from 'rxjs/observable/empty'
import { Observable } from 'rxjs/Observable'
import { Option } from 'fp-ts/lib/Option'
import { perform, Task } from 'elm-ts/lib/Task'
import { Program, Html } from 'elm-ts/lib/Html'
import { program, Location } from 'elm-ts/lib/Navigation'
import { Reader, asks, reader } from 'fp-ts/lib/Reader'

// The new Cmd Signature
export type CmdR<env, msg> = Reader<env, Observable<Task<Option<msg>>>>

export const none: CmdR<any, never> = reader.of(empty())

export type SubR<env, msg> = Reader<env, Observable<msg>>

// Adaption of program to accept CmdR for init, update and subscriptions
export const programWithReader = <env, model, msg, dom>(
  locationToMessage: (location: Location) => msg,
  init: (location: Location) => [model, CmdR<env, msg>],
  update: (msg: msg, model: model) => [model, CmdR<env, msg>],
  view: (model: model) => Html<dom, msg>,
  subscriptions?: (model: model) => SubR<env, msg>
): Reader<env, Program<model, msg, dom>> => {
  return asks((env) => program<model, msg, dom>(
    locationToMessage,
    location => {
      const [model, cmd] = init(location)
      return [model, cmd.run(env)]
    },
    (msg, model) => {
      const [newModel, cmd] = update(msg, model)
      return [newModel, cmd.run(env)]
    },
    view,
    model => subscriptions ? subscriptions(model).run(env) : empty()
  ))
}

// Cause our side effect is wrapped in a Reader we need to wrap perform accordently
export const performR = <a, env, msg>(readerTask: Reader<env, Task<a>>, f: (a: a) => msg): CmdR<env, msg> => {
  return readerTask.map(task => perform(task, f))
}

I limited the access to the Reader to the commands to prevent the leaking of effectfull dependencies to the rest of the application (view, init and update functions).

I used this code in your elm-ts-todomvc example to make localStorage to be exchangable in unit tests or to make the app server side renderable (where no localStorage is avaibale).

Would this be something reasonable to integrate in elm-ts itself?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions