Giter Club home page Giter Club logo

alacarte's Introduction

Data types à la carte in JavaScript

中文 🇨🇳

It’s pretty funny though, this is a simple implementation that port Data Types à la Carte (it would be awfully help if you can read this paper first) from Haskell to JavaScript. It will solve the particular problem in react-most but you can use this technique with any other flux e.g. redux or DSL(expression + interpreter)

Install

yarn add alacarte.js

TLDR;

BEFORE (The Expression Problem)

  const Intent = Type({
    Inc: [Number],
    Dec: [Number],
+   Mult: [Number],
  })
  const increasable = connect(intent$ => {
    return {
      sink$: intent$.map(Intent.case({
        Inc: (v) => over(lensCount, x=>x+v),
        Dec: (v) => over(lensCount, x=>x-v),
+       Mult: (v) => over(lensCount, x=>x*v),
        _: () => identity
      })),
      actions: {
        inc: Intent.Inc,
        dec: Intent.Dec,
+       mult: Intent.Mult
      }
    }
  })

AFTER (Data Types a la carte)

  const {Add} = Expr.create({ Add: ['fn'] })
  const evalAdd = interpreterFor(Add, function (v) {
    return x => x + v.fn(x)
  });

  const evalVal = interpreterFor(Val, function (v) {
    return ()=> v.value
  });

  const evalOver = interpreterFor(Over, function (v) {
    let newstate = {}
    let prop = v.prop()
    return state => (newstate[prop] = v.fn(state[prop]), newstate)
  });

- let interpreter = interpreterFrom([evalLit, evalAdd, evalOver])
- let injector = injectorFrom([Val, Add, Over])
- let [val, add, over] = injector.inject()

+ const {Mult} = Expr.create({ Mult: ['fn'] })
+ const evalMult = interpreterFor(Mult, function (v) {
+   return x => x * v.fn(x)
+ });

+ let injector = injectorFrom([Val, Add, Over, Mult])
+ let interpreter = interpreterFrom([evalLit, evalAdd, evalOver, evalMult])
+ let [val, add, over, mult] = injector.inject()

  const counterable = connect((intent$) => {
    return {
      sink$: intent$.filter(isInjectedBy(injector))
                    .map(interpretExpr(interpreter)),
      inc: v => over(val('count'), add(val(v))),
      dec: v => over(val('count'), add(val(-v))),
+     mult: v => over(val('count'), mult(val(v))),
    }
  })

Example

Why

The problem of react-most or any flux is that when a Action is dispatched, something like a reducer will have to evaluate it and produce something to change state.

Which means, you have to define all your Actions in one place so that any reducer can switch on them. e.g. in react-most there is a big switch, you’ve probably see lot of these in redux as well.

It’s global thing, anyone want to add a new Action will have to change it.

The Expression Problem that Data Types à la Carte try to solve is pretty much the same as our problem if we map the concept of Action to Expression, and Reducer to Interpreter.

From Object Algebras to Finally Tagless Interpreters has better explaination of Expression Problem than me

[[https://oleksandrmanzyuk.files.wordpress.com/2014/06/wpid-2014-06-19-232942.png]]

How

With Data Types à la Carte, we now can define Actions anywhere, anytime, further more, it’ll let us finally get rid of ugly switch case.

note the difference here

with Data Types à la Carte, you reducer will be “Type” safe and declarative. You’ll probably confuse what the hell is isInjectedBy or injector, I’ll explain this further but now you should able to see the logic is pretty declarative and straightforward here.

it just filter from all the Expressions where they only the same Type as injector, then interpret these expressions with interpreter

Expression

let {Add, Over} = Expr.create({
  Add: ['fn'],
  Over: ['prop', 'fn']
})

Add is the name of the expression and ['fn'] means it contains a value named fn. since over need a function so Add should contains a function.

Over has value prop and fn

Interpreter

then, create interpreter for each of them

// Instances of Interpreters
const evalAdd = interpreterFor(Add, function (v) {
  return x => x + v.fn(x)
});

const evalVal = interpreterFor(Val, function (v) {
  return ()=> v.value
});

const evalOver = interpreterFor(Over, function (v) {
  let newstate = {}
  let prop = v.prop()
  return state => (newstate[prop] = v.fn(state[prop]), newstate)
});

the Val Type is built in alacarte.js so you don’t need to define the expression type, just simply =import {Val} from ‘alacarte.js’= and implement it’s interpreter.

compose these interpreters

let interpreter = interpreterFrom([evalLit, evalAdd])

Injector

create a injector from these functor types

let injector = injectorFrom([Val, Add, Over])

now inject the injector will generate a list of expression constructor

let [val, add, over] = injector.inject()

Add a new Expression Mult

after all this, let’s see how easy to add a new expression with modify any of the existing expressions and there interpreter

  • a ADT of Mult
// a new mult expr is add without modify any of the current code
let {Mult} = Expr.create({
  Mult: ['fn'],
})
const evalMult = interpreterFor(Mult, function (v) {
  return x => x * v.fn(x)
});

let printMult = interpreterFor(Mult, function (v) {
  return `(_ * ${v.fn})`
});

Nothing has been modify in existing code, a new expression and it’s interpreter just works now.

a new Interpreter

say we want another interpreter for the expr, like printer

const printAdd = interpreterFor(Add, function (v) {
  return `(_ + ${v.fn})`
});

const printVal = interpreterFor(Val, function (v) {
  return v.value.toString()
});

const printOver = interpreterFor(Over, function (v) {
  return `over ${v.prop} do ${v.fn}`
});

const printMult = interpreterFor(Mult, function (v) {
  return `(_ * ${v.fn})`
});

interpert the expr will print out the expression

interpretExpr(printer)(expr)

will print count + (count * 2)

alacarte's People

Contributors

jcouyang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.