npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

morphic

v1.0.16

Published

Ad-hoc polymorphism / pattern matching / destructuring for function parameters

Downloads

85

Readme

Morphic

Node.js CI FOSSA Status

Support for writing ad-hoc polymorphic functions. These are functions that will accept different types of arguments. They can execute different sets of code depending on the shape of the information represented in the arguments

Author: Olli Jones
License: MIT
npm install morphic --save

Example usage

Look in ./test/implementations.js for more examples

var morphic = require("morphic");
var assert = require("assert");

// We can define a function "getName":
var getName = new morphic();

// To understand book authors:
getName.with({
  author: morphic.String("name")
}).returnNamedMatch("name");

// and website owners:
getName.with({
  owner: morphic.String("name")
}).returnNamedMatch("name");

// and to fallback gracefully:
getName.otherwise().return("unknown");

// We can see our function working with books:
assert.equal(
  getName({
    title: "1984",
    author: "George Orwell"
  }),
  "George Orwell");

// and websites:
assert.equal(
  getName({
    url: "www.github.com",
    owner: "GitHub, Inc"
  }),
  "GitHub, Inc");

// and even on unexpected, but coercible, input such as a name in an array:
assert.equal(
  getName({
    title: "Atlas Shrugged",
    author: ["Ayn Rand"]
  }),
  "Ayn Rand");

// and see it falling back gracefully:
assert.equal(
  getName(123),
  "unknown");

API documentation

A quick guide to the language used in this documentation:

  • instance an instance of a morphic function, created through new morphic()
  • shape/structure/type people tend to use this language interchangeably to describe the format of the arguments to the function
  • matchers a way of defining the shape input will form
  • implementation the way a particular type will be processed, roughly the lambda in ...then((named_matchers) => ...)
  • named matchers naming a matcher will make the matching input available through the first argument of an implementation

A Morphic Function

  1. Create a morphic instance with var myMethod = new morphic() or var myMethod = morphic()
  2. Add the implementations using matchers and the myMethod.with(...) and myMethod.otherwise()
  3. Call the method as a normal function, like var result = myMethod(...)

Matchers

To help define the sorts of shapes/structures/types an implementation is able to process we need matchers, these allow us to algorithmically express these types.

Each matcher can have an associated name to turn it into a named matcher which will make its matched value explicitly available through the first argument to an implementation

morphic.null([name])

Returns a matcher that matches x == null

morphic.undefined([name])

Returns a matcher that matches x == undefined

morphic.Object([name])

Returns a matcher that matches something that can be coerced to an Object, when using multiple matchers you might want to use the exact type match companion morphic.object([name]).

morphic.Boolean([name])

Returns a matcher that matches something that can be coerced to a Boolean, when using multiple matchers you might want to use the exact type match companion morphic.boolean([name]).

morphic.Number([name])

Returns a matcher that matches something that can be coerced to a Number, when using multiple matchers you might want to use the exact type match companion morphic.number([name]).

morphic.String([name])

Returns a matcher that matches something that can be coerced to a String, when using multiple matchers you might want to use the exact type match companion morphic.string([name]).

morphic.object([name])

Returns a matcher that matches something that must be an object, it will not attempt to type coerce the input. For a more liberal matcher, use the Capitalised companion morphic.Object([name]).

morphic.boolean([name])

Returns a matcher that matches something that must be an boolean, it will not attempt to type coerce the input. For a more liberal matcher, use the Capitalised companion morphic.Boolean([name]).

morphic.number([name])

Returns a matcher that matches something that must be a number, it will not attempt to type coerce the input. For a more liberal matcher, use the Capitalised companion morphic.Number([name]).

morphic.string([name])

Returns a matcher that matches something that must be an string, it will not attempt to type coerce the input. For a more liberal matcher, use the Capitalised companion morphic.String([name]).

morphic.symbol([name])

Returns a matcher that matches something that must be a symbol.

morphic.function([name])

Returns a matcher that matches something that must be a function.

morphic.either(options, [name])

Returns a matcher that will match on any matcher expressed in the array of option

morphic.literally(value, [name])

Returns a matcher that matches x == value this should be used in the either matcher - it is implicitly used elsewhere

Interacting with a Morphic Function

There are different methods of interacting with a morphic instance. Firstly adding implementations. These are essentially the body of the method. The order in which you add implementations doesn't matter, morphic will call the implementation with the shape most similar to the inputs given

Assuming myMethod is an instance such as var myMethod = new morphic():

myMethod.with(shape1, [shape2, [shape3...]])...

Define the shape of each argument using matchers, objects and literals, ie: myMethod.with("string", 123, morphic.Boolean())... would match both myMethod("string", 123, true) and myMethod("string", 123, false)

myMethod.with(...).then(implementation)

Calling with...then will execute the implementation when the inputs match the shapes given in the with call. The implementation will be called as implementation(named_matchers_map, original_argument0, ...) where the named_matchers_map will be an Object where each key in the name of the matcher and the value is the matched input

myMethod.with(...).throw(error)

Calling with...throw will throw the given error when the input matches. The error must be given as an instance, such as new Error("error message")

myMethod.with(...).return(value)

Calling with...return will return the given value when the input matches

myMethod.with(...).returnArgument(n)

Calling with..returnArgument will return the specified input argument when the input matches. Arguments are zero indexed, so returnArgument(0) would return the first argument

myMethod.with(...).returnNamedMatch(name)

Calling with...returnNamedMatch will return the value of the named matcher when the input matches

myMethod.otherwise()...

Otherwise will let you provide a fallback implementation that will be called in the case that no existing implementations are suitable. Note that when the input matches multiple implementations and all are similarly specific then an exception will still be thrown, this otherwise clause will not be activated

User defined matchers

Where the above matchers aren't enough a new one can be defined using:

var myMatcher = morphic.makeMatcher((input) => return wasMatched)

myMatcher is now similar to the above matchers, use it in the same way with myMatcher([name])

Troubleshooting common errors

Morphic will throw a couple of different errors. This section covers how to troubleshoot them.

2,3,4,... methods match on the input ...

This occurs when more than a single method matches the input. Each matching method will be listed at the bottom of the stack trace.

Common pitfalls that can cause this error are using matchers that overmatch the input, such as using two Capitalised matchers which will try to type coerce:

var overMatches = new morphic();
overMatches.with(morphic.String()).return(0);
overMatches.with(morphic.Number()).return(1);
overMatches(1);

This example will throw because the input 1 can be coerced to a string or a number, therefore both methods are valid options. Use a lowercase method such as morphic.string(...) or morphic.number(...).

Another pitfalls is to use morphic.anything() which will match anything, including undefined and null. Use undefined or null explicitly when you don't want something to exist.

No methods matched input ...

Using a fallback can side step this issue, see the documentation for myMethod.otherwise()....

Check that the input is well formed, and make sure that the method isn't being called before any implementations are added.

Running tests

npm install
npm test

Known issues

  • Although rare, hash collisions may cause morphic to use the same matcher instead of two unique matchers on an input

Future work

  • The existing stack can be organized into a binary search tree where each node expresses the matcher and it has a truthy and falsey leaf. Arranging to create a more balanced tree will improve performance
  • The matchers can be used as part of a code generation step to produce more efficient code - even evaling the output will result in a more performant matcher