{"_id":"@banzai-inc/multimethod","name":"@banzai-inc/multimethod","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"author":{"name":"Kris Jordan","email":"krisjordan@gmail.com","url":"http://krisjordan.com"},"name":"@banzai-inc/multimethod","description":"Multimethods for JavaScript","version":"0.1.0","repository":{"type":"git","url":"git://github.com/banza-inc/multimethod-js.git"},"dependencies":{"underscore":"1.2.1"},"devDependencies":{},"gitHead":"4b19ef2a9aee29a8f7dfa1761a76c43b22ead430","bugs":{"url":"https://github.com/banza-inc/multimethod-js/issues"},"homepage":"https://github.com/banza-inc/multimethod-js#readme","_id":"@banzai-inc/multimethod@0.1.0","_nodeVersion":"18.15.0","_npmVersion":"8.19.2","dist":{"integrity":"sha512-NZ2lSmkK8eTdfzCNphPCCwqjpBokA/gdjfX4NmCRgDIFYZYzJH8ubZwNzjDuZi42CuNvojgG642dkff5gBdcrg==","shasum":"7c093529ebce9d1d7bd3da8428aedec5c30761f5","tarball":"https://registry.npmjs.org/@banzai-inc/multimethod/-/multimethod-0.1.0.tgz","fileCount":15,"unpackedSize":217950,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIE+txHyzrQzx3YkRPMhHofn1n4cHCwerLksKICk0FKCzAiBdc4r3AfvelOVfr4WM6HeGOpK7eGLbWt6AUvaguuhnLA=="}]},"_npmUser":{"name":"jaredbanzai","email":"jared@banzai.org"},"directories":{},"maintainers":[{"name":"jaredbanzai","email":"jared@banzai.org"},{"name":"buchanankendall","email":"kendall@teachbanzai.com"},{"name":"austinhollenbaugh","email":"austinhollenbaugh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/multimethod_0.1.0_1684508020219_0.8344519761647617"},"_hasShrinkwrap":false}},"time":{"created":"2023-05-19T14:53:40.139Z","0.1.0":"2023-05-19T14:53:40.469Z","modified":"2023-05-19T14:53:40.681Z"},"maintainers":[{"name":"jaredbanzai","email":"jared@banzai.org"},{"name":"buchanankendall","email":"kendall@teachbanzai.com"},{"name":"austinhollenbaugh","email":"austinhollenbaugh@gmail.com"}],"description":"Multimethods for JavaScript","homepage":"https://github.com/banza-inc/multimethod-js#readme","repository":{"type":"git","url":"git://github.com/banza-inc/multimethod-js.git"},"author":{"name":"Kris Jordan","email":"krisjordan@gmail.com","url":"http://krisjordan.com"},"bugs":{"url":"https://github.com/banza-inc/multimethod-js/issues"},"readme":"# What is multimethod.js? \n\nMultimethods are a functional programming control structure that allow you\nto dynamically build-up and manipulate the dispatching behavior of a \npolymorphic function. Inspired by clojure's multimethods, multimethod.js \nprovides a functional alternative to classical, prototype based polymorphism. \nThe multimethod.js library is 1kb minified, MIT licensed, and available on\n[GitHub](https://github.com/KrisJordan/multimethod-js).\n\n# Installation\n\nInstall with `npm` for use in node.js based projects.\n\n    npm install multimethod\n    node\n    > var multimethod = require('multimethod');\n\nFor in-browser use you will need to grab \n[underscore.js](http://documentcloud.github.com/underscore/) and multimethod.js:\n\n- underscore.js\n  - Development: http://documentcloud.github.com/underscore/underscore.js\n  - Minified: http://documentcloud.github.com/underscore/underscore-min.js\n- multimethod.js\n  - Development: https://raw.github.com/KrisJordan/multimethod-js/master/multimethod.js\n  - Minified: https://raw.github.com/KrisJordan/multimethod-js/master/multimethod-min.js\n\n# API\n\n- Constructor: `multimethod`( [fn | string] ):  No arg constructor uses an\n  identity function for `dispatch`. Single arg constructor is a shortcut for\n  calling `dispatch` with the same argument.\n- `dispatch`(fn | string): Sets the `multimethod`'s `dispatch` function. String\n  values are transformed into a pluck function which projects a single\n  property value from the first argurment.\n- `when`(match, fn | value): Add a `method` to be invoked when the `dispatch`\n  return value matches 'match'. If a non-function `value` is provided it will\n  be returned directly. Calling `when` with the same `match` value twice will \n  override the previously registered `method`.\n- `remove`(match): Remove a `method` by it's `match` value.\n- `default`(fn | value): Catch-all case when no `method` match is found.\n\n\n# Motivating Examples\n\nLet's use the node.js REPL to build a few multimethods and see what they are\ncapable of doing. In this first example we'll create a mulimethod that\ncalculates the area of shapes instantiated with object literals.\n\n```javascript\n> var multimethod = require('multimethod');\n> var area = multimethod()\n                .dispatch(function(o) {\n                    return o.shape;\n                })\n                .when(\"square\", function(o) {\n                    return Math.pow(o.side, 2);\n                });\n> var aSquare = { \"shape\":\"square\", \"side\": 2 };\n> area( aSquare );\n4\n\n> var aCircle = { \"shape\":\"circle\", \"radius\": 5 };\n> area( aCircle );\nundefined\n\n> area.default(function(o) { \n    throw \"Unknown shape: \" + o.shape;\n  });\n> area( aCircle );\nUnknown Shape: circle\n\n> area.when(\"circle\", function(o) {\n    return Math.PI * Math.pow(o.radius, 2);\n  });\n> area( aCircle );\n78.53981633974483\n> area( aSquare );\n4\n\n> area.remove(\"circle\");\n> area( aCircle );\nUnknown Shape: circle\n```\n\nNotice how `dispatch` returns the value we'll match to a \"method\" registered\nwith `when`. You can introduce, overwrite, and remove new methods dynamically at\nruntime. Fallback behavior can be established with a `default` function called\nwhen no methods match the dispatched value.\n\nA recursive Fibonacci function can be expressed naturally with a multimethod.\n\n```javascript\n> var fib = multimethod()\n                .when( 0, 0 )\n                .when( 1, 1 )\n                .default( function(n) {\n                    return fib(n-1) + fib(n-2);\n                });\n> fib(20);\n6765\n```\n\nNotice, there is no `dispatch` specified. By default a multimethod will use\nthe first argument it is invoked with to match the correct method to dispatch\nto.\n\n```javascript\n> var hitPoints = multimethod()\n                    .dispatch(function(player){ return player.powerUp; })\n                    .when( {\"type\":\"star\"} , Infinity)\n                    .default(5);\n\n> var starPower = { \"type\":\"star\" },\n>     mario = { \"powerUp\": starPower };\n> hitPoints(mario);\nInfinity\n\n> mario.powerUp = null;\n> hitPoints(mario);\n5\n\n> var godModeCheat = function() { return starPower; };\n> hitPoints.dispatch(godModeCheat);\n> mario.powerUp;\nnull\n> hitPoints(mario);\nInfinity\n```\n\nIn this last example notice how we are matching against an object. Matching \nis done using deep equality so objects and arrays are valid method matching\ncriteria.  Also notice how we can completely override our dispatch \nfunction to change the logic with which a multimethod evaluates its arguments\nfor dispatch, or, in this case, ignores them!\n\n# Multimethod Dispatch Algorithm Overview\n\n1. User calls multimethod with argument `anArgument`.\n2. Multimethod calls its `dispatch` function with `anArgument`. The returned \n   value is stored in `dispatchValue`.\n3. Multimethod iterates through each 'method' registered with `when` and \n   performs an equality test on the `dispatchValue` and each method's match\n   value. If a match is found, set `matchFunction` to the method's function \n   and go to step 5.\n4. If no method match found, set `matchFunction` to the multimethod's `default`\n   function.\n5. Multimethod calls `matchFunction` with `anArgument`. The returned value\n   is returned to the user who called the multimethod.\n\n# Detailed Walkthrough\n\n## The Basics\n\nA `multimethod` is instantiated with the `multimethod` function.\n\n```javascript\nvar stopLightColor = multimethod();\n```\n  \nA `multimethod` has methods. A `method` is has two parts, its match value\nand its implementation function. Methods are added using `when`.\n\n```javascript\nstopLightColor.when(\"go\",    function() { return \"green\"; })\n              .when(\"stop\",  function() { return \"red\"; });\n```\n\nYou can call a `multimethod` just like any other function. It will dispatch\nbased on the argument(s) passed in, invoke the matched `method`, and return \nthe results of the `method` call.\n\n```javascript\nconsole.log( stopLightColor(\"go\") ); // \"green\"\n```\n\nWhen no method matches control can fallback to a `default` method.\n\n```javascript\nstopLightColor.default( function() { return \"unknown\"; } );\nconsole.log( stopLightColor(\"yield\") ); // prints \"unknown\"\n```\n\nA `multimethod` can handle new cases dynamically at run time.\n\n```javascript\nstopLightColor.when(\"yield\", function() { return \"yellow\"; });\n```\n\nThere is a shorter way for a `method` to return a plain value. Rather than \npassing an implementation function to `when`, pass the value. \n\n```javascript\nstopLightColor.when(\"yield\", \"yellow\");\nconsole.log( stopLightColor(\"yield\") ); // prints \"yellow\"\n```\n\nA `method` can be removed dynamically at run time, too.\n\n```javascript\nstopLightColor.remove(\"go\");\nconsole.log( stopLightColor(\"go\") ); // prints \"unknown\"\n```\n\n## Dispatch Function\n\nEach `multimethod` call first invokes a `dispatch` function whose return value\nis used to match the correct `method` to call. The `dispatch` function is \npassed the arguments the `multimethod` is invoked with and returns a value\nto match against.\n\nThe default `dispatch` function is an identity function. \nThe basic `stopLightColor` examples could have been \ncreated with an explicit `dispatch` function.\n\n```javascript\nvar stopLightColor = multimethod()\n      .dispatch(function(state){\n         return state;\n      })\n      .when('go', 'green');\nconsole.log( stopLightColor('go') ); // green\n```\n\nThe power of the `multimethod` paradigm is the ability to dispatch with a\nuser-defined function. This gives a `multimethod` its \"polymorphic\" powers. \nUnlike classical, object-oriented polymorphism where the compiler dispatches \nbased on the type hierarchy, a `multimethod` can dispatch on any criteria.\n\n```javascript\nvar contacts = [\n  {\"name\":\"Jack\", \"service\":\"Twitter\",\"handle\": \"@jack\"},\n  {\"name\":\"Diane\",\"service\":\"Email\",  \"address\":\"d@g.com\"},\n  {\"name\":\"John\", \"service\":\"Phone\",  \"number\": \"919-919-9191\"}\n];\n\nvar sendMessage = multimethod()\n     .dispatch(function(contact, msg) {\n       return contact.service;\n     })\n     .when(\"Twitter\", function(contact, msg) {\n       console.log(\"Tweet @\"+contact.handle+\":\"+msg);\n     })\n     .when(\"Email\", function(contact, msg) {\n       console.log(\"Emailing \"+contact.address+\":\"+msg);\n     })\n     .default(function(contact, msg) {\n       console.log(\"Could not message \" + contact.name);\n     });\n\n// Blast a message\ncontacts.forEach( function(contact) {\n  sendMessage(contact, \"Hello, world.\"); \n});\n```\n\nPlucking a single property from an object is so commonly used as a `dispatch`\nfunction, like in the example above, there is a shortcut for this pattern. \nThe following `dispatch` call is equivalent to above.\n\n```javascript\nsendMessage.dispatch( 'service' );\n```\n\nA `multimethod`'s `dispatch` is usually specified when constructed.\n\n```javascript\nvar sendMessage = multimethod('service');\n```\n\nJust like `method`s can be added and removed from a `multimethod` at \nrun time, the `dispatch` function can also be redefined at run time.\nPonder the implications of that for a minute. It is really powerful and \nreally dangerous. Don't shoot your eye out.\n\n## Deep Equality Matching\n\nA `method`'s match value is compared to `dispatch`'s return value \nusing the underscore.js \n[`isEqual`](http://documentcloud.github.com/underscore/#isEqual)\nfunction. Deep equality `method` matching enables concise expressivity.\nContrast this with a traditional `switch` statement that is\nlimited by JavaScript's === equality behavior.\n\n```javascript\nvar greatPairs = multimethod()\n      .when( [\"Salt\", \"Pepper\"], \"Shakers\" )\n      .when( [{\"name\":\"Bonnie\"}, {\"name\":\"Clyde\"}], \"Robbers\" );\nconsole.log( greatPairs( [\"Salt\", \"Pepper\"] ) ); // Shakers\n```\n\n## Related Work\n\n* Clojure's multimethod - http://clojure.org/multimethods\n* Pascal Coste Filtered Dispatch in Common Lisp - http://www.p-cos.net/documents/filtered-dispatch.pdf\n\n## How-to Contribute\n\n* Submit bugs and feature requests on \n[GitHub Issues](https://github.com/KrisJordan/multimethod-js/issues) page.\n* Fork the repository and submit pull requests. Pull requests that update\n  the test suite for coverage on changes will be brought in quickly.\n","readmeFilename":"README.md"}