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

@whimsical-code/multimethod

v1.0.1

Published

TypeScript implementation of Clojure's multimethods.

Downloads

143

Readme

Clojure-like multimethods for JavaScript/TypeScript

This is a simplified implementation of Clojure's multimethods that only supports strings as dispatch values and doesn't support hierarchical dispatch.

Typical usage for multimethods is dependency injection. For example if you are building a whiteboarding app like Whimsical, you'll have bunch of different object types on canvas such as shape, image, text, etc.

Multimethods allow you to define a generic method signature, for example drawObject and then add implementation for each object type elsewhere in the codebase. The key is that the code that defines and uses the multimethod doesn't need to know about the different object types.

If you come from OOP background, this is similar to defining an abstract method in a base class and then implementing it in subclasses. The difference is that multimethods work on pure data, not classes and unlike with classes, implementation may change dynamically if the object data changes (f.e. shape type is changed from rectangle to ellipse).

Installation

Either copy multimethod.ts to your project (it's only 40 lines of code) or install it from npm:

npm install @whimsical-code/multimethod

Example usage

import { multimethod } from "@whimsical-code/multimethod";

interface Shape {
  type: "rectangle" | "ellipse";
  width: number;
  height: number;
}

const shapeArea = multimethod(
  (shape: Shape) => shape.type // dispatch function that should return a string
);

shapeArea.method("rectangle", (shape) => shape.width * shape.height);
shapeArea.method("ellipse", (shape) => Math.PI * shape.width * shape.height / 4);

shapeArea({ type: "rectangle", width: 2, height: 4 }); // 8
shapeArea({ type: "ellipse", width: 2, height: 4 }); // 6.28319

If there's no method implementation defined for a given dispatch value, by default the multimethod invocation will throw an error. Alternatively you can provide the default method implementation that will be invoked whenever there is no implementation for a dispatch value:

const echo = multimethod(
  (value: string) => value,
  (value: string) => `default method invoked for ${value}` // default method implementation
)
.method("foo", (_) => "foo") // method implementations can also be chained
.method("bar", (_) => "bar");

echo("foo"); // foo
echo("bar"); // bar
echo("baz"); // default method invoked for baz

Typescript usage

To get a fully typed multimethod signature you have two options:

  1. Define multimetod with a default method implementation. In this case the multimethod type can be fully inferred:

    // echo is inferred as (string) => string
    const echo = multimethod(
      (value: string) => value,
      (value: string) => `default method invoked for ${value}` // default method implementation
    );
  2. Explicitly type the multimethod:

    // <array of method argument types, dispatch value type, method return type>
    // Explicit typing is neccessary because it's not possible to infer return value in this form.
    const echo = multimethod<[string], string, string>(
      (value: string) => value,
    );

Caveats

In a larger codebase you'll typically want to have multimethod implementations in separate source files from the definition. If you are using a JavaScript bundler with tree-shaking (such as Rollup/Vite), you'll need to make sure that all the files that implement multimethods actually get evaluated.

If you have files that only implement multimethods, you'll want to import them in the main entry file and make sure those files are marked as having side effects. Here's example for Vite:

export default defineConfig({
  build: {
    rollupOptions: {
      treeshake: {
        moduleSideEffects: ['some-file-that-implements-multimethods.ts'],
      },
    }
  }
});