{"id":21399387,"url":"https://github.com/jcouyang/alacarte","last_synced_at":"2025-07-13T20:33:28.110Z","repository":{"id":57175407,"uuid":"86804256","full_name":"jcouyang/alacarte","owner":"jcouyang","description":"Data Types a la carte from PureScript -\u003e JavaScript","archived":false,"fork":false,"pushed_at":"2017-04-19T09:47:13.000Z","size":123,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-04T02:07:48.153Z","etag":null,"topics":["a-la-carte","data-type"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jcouyang.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-31T09:49:15.000Z","updated_at":"2023-03-08T02:11:41.000Z","dependencies_parsed_at":"2022-09-03T11:00:56.979Z","dependency_job_id":null,"html_url":"https://github.com/jcouyang/alacarte","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/jcouyang/alacarte","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcouyang%2Falacarte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcouyang%2Falacarte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcouyang%2Falacarte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcouyang%2Falacarte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jcouyang","download_url":"https://codeload.github.com/jcouyang/alacarte/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jcouyang%2Falacarte/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265200067,"owners_count":23726768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a-la-carte","data-type"],"created_at":"2024-11-22T15:14:20.492Z","updated_at":"2025-07-13T20:33:27.795Z","avatar_url":"https://github.com/jcouyang.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"* /Data types à la carte/ in JavaScript\n\n[[https://github.com/jcouyang/alacarte/wiki/%E8%AF%BB%E6%88%91][中文 :cn:]]\n\nIt's pretty funny though, this is a simple implementation that port [[http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=4B1BB52114FB29D3169B1761C3FBFF15?doi=10.1.1.101.4131\u0026rep=rep1\u0026type=pdf][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 [[https://github.com/reactive-react/react-most][react-most]] but you can use this technique with any other flux e.g. redux or DSL(expression + interpreter)\n** Install\n#+BEGIN_SRC sh\nyarn add alacarte.js\n#+END_SRC\n\n** TLDR;\n*** BEFORE (The Expression Problem)\n#+BEGIN_SRC diff\n  const Intent = Type({\n    Inc: [Number],\n    Dec: [Number],\n+   Mult: [Number],\n  })\n  const increasable = connect(intent$ =\u003e {\n    return {\n      sink$: intent$.map(Intent.case({\n        Inc: (v) =\u003e over(lensCount, x=\u003ex+v),\n        Dec: (v) =\u003e over(lensCount, x=\u003ex-v),\n+       Mult: (v) =\u003e over(lensCount, x=\u003ex*v),\n        _: () =\u003e identity\n      })),\n      actions: {\n        inc: Intent.Inc,\n        dec: Intent.Dec,\n+       mult: Intent.Mult\n      }\n    }\n  })\n#+END_SRC\n\n*** AFTER (Data Types a la carte)\n#+BEGIN_SRC diff\n  const {Add} = Expr.create({ Add: ['fn'] })\n  const evalAdd = interpreterFor(Add, function (v) {\n    return x =\u003e x + v.fn(x)\n  });\n\n  const evalVal = interpreterFor(Val, function (v) {\n    return ()=\u003e v.value\n  });\n\n  const evalOver = interpreterFor(Over, function (v) {\n    let newstate = {}\n    let prop = v.prop()\n    return state =\u003e (newstate[prop] = v.fn(state[prop]), newstate)\n  });\n\n- let interpreter = interpreterFrom([evalLit, evalAdd, evalOver])\n- let injector = injectorFrom([Val, Add, Over])\n- let [val, add, over] = injector.inject()\n\n+ const {Mult} = Expr.create({ Mult: ['fn'] })\n+ const evalMult = interpreterFor(Mult, function (v) {\n+   return x =\u003e x * v.fn(x)\n+ });\n\n+ let injector = injectorFrom([Val, Add, Over, Mult])\n+ let interpreter = interpreterFrom([evalLit, evalAdd, evalOver, evalMult])\n+ let [val, add, over, mult] = injector.inject()\n\n  const counterable = connect((intent$) =\u003e {\n    return {\n      sink$: intent$.filter(isInjectedBy(injector))\n                    .map(interpretExpr(interpreter)),\n      inc: v =\u003e over(val('count'), add(val(v))),\n      dec: v =\u003e over(val('count'), add(val(-v))),\n+     mult: v =\u003e over(val('count'), mult(val(v))),\n    }\n  })\n#+END_SRC\n\n*** Example\n- [[https://reactive-react.github.io/react-most/examples/alacarte/public/][try it!]]\n- [[https://github.com/reactive-react/react-most/tree/master/examples/alacarte][source]]\n\n** Why\nThe 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.\n\nWhich means, you have to define all your Actions in one place so that any reducer can switch on them. e.g. in react-most [[https://github.com/reactive-react/react-most/blob/master/examples/counter/src/app.jsx#L18][there is a big switch]], you've probably see lot of these in redux as well.\n\nIt's global thing, anyone want to add a new Action will have to change it.\n\n[[https://en.wikipedia.org/wiki/Expression_problem][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=.\n\n[[https://oleksandrmanzyuk.wordpress.com/2014/06/18/from-object-algebras-to-finally-tagless-interpreters-2/][From Object Algebras to Finally Tagless Interpreters]] has better explaination of Expression Problem than me\n\n[[[[https://oleksandrmanzyuk.files.wordpress.com/2014/06/wpid-2014-06-19-232942.png]]]]\n\n** How\nWith Data Types à la Carte, we now can define Actions anywhere, anytime, further more, it'll let us finally get rid of ugly switch case.\n\nnote the difference here\n\nwith 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.\n\n#+BEGIN_QUOTE\nit just filter from all the =Expressions= where they only the same =Type= as =injector=, then interpret these expressions with =interpreter=\n#+END_QUOTE\n\n** Expression\n\n#+BEGIN_SRC js\nlet {Add, Over} = Expr.create({\n  Add: ['fn'],\n  Over: ['prop', 'fn']\n})\n#+END_SRC\n=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.\n\n=Over= has value =prop= and =fn=\n\n** Interpreter\nthen, create interpreter for each of them\n#+BEGIN_SRC js\n// Instances of Interpreters\nconst evalAdd = interpreterFor(Add, function (v) {\n  return x =\u003e x + v.fn(x)\n});\n\nconst evalVal = interpreterFor(Val, function (v) {\n  return ()=\u003e v.value\n});\n\nconst evalOver = interpreterFor(Over, function (v) {\n  let newstate = {}\n  let prop = v.prop()\n  return state =\u003e (newstate[prop] = v.fn(state[prop]), newstate)\n});\n#+END_SRC\n\nthe =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.\n\ncompose these interpreters\n#+BEGIN_SRC js\nlet interpreter = interpreterFrom([evalLit, evalAdd])\n#+END_SRC\n** Injector\ncreate a injector from these functor types\n#+BEGIN_SRC js\nlet injector = injectorFrom([Val, Add, Over])\n#+END_SRC\n\nnow inject the injector will generate a list of expression constructor\n\n#+BEGIN_SRC js\nlet [val, add, over] = injector.inject()\n#+END_SRC\n\n** Add a new Expression Mult\nafter all this, let's see how easy to add a new expression with modify any of the existing expressions and there interpreter\n\n- a ADT of Mult\n#+BEGIN_SRC js\n// a new mult expr is add without modify any of the current code\nlet {Mult} = Expr.create({\n  Mult: ['fn'],\n})\nconst evalMult = interpreterFor(Mult, function (v) {\n  return x =\u003e x * v.fn(x)\n});\n\nlet printMult = interpreterFor(Mult, function (v) {\n  return `(_ * ${v.fn})`\n});\n#+END_SRC\n\nNothing has been modify in existing code, a new expression and it's interpreter just works now.\n\n** a new Interpreter\nsay we want another interpreter for the expr, like printer\n#+BEGIN_SRC js\nconst printAdd = interpreterFor(Add, function (v) {\n  return `(_ + ${v.fn})`\n});\n\nconst printVal = interpreterFor(Val, function (v) {\n  return v.value.toString()\n});\n\nconst printOver = interpreterFor(Over, function (v) {\n  return `over ${v.prop} do ${v.fn}`\n});\n\nconst printMult = interpreterFor(Mult, function (v) {\n  return `(_ * ${v.fn})`\n});\n#+END_SRC\n\ninterpert the expr will print out the expression\n#+BEGIN_SRC js\ninterpretExpr(printer)(expr)\n#+END_SRC\nwill print =count + (count * 2)=\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcouyang%2Falacarte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcouyang%2Falacarte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcouyang%2Falacarte/lists"}