git.fiddlerwoaroof.com
Raw Blame History
#+TITLE: README for js-generic-functions
#+AUTHOR: Ed L
#+HTML_HEAD: <link rel="stylesheet" href="./colors.css" />
#+EXPORT_FILE_NAME: docs/index.html

[[https://www.npmjs.com/package/js-generic-functions][https://img.shields.io/npm/v/js-generic-functions.svg]] [[https://circleci.com/gh/fiddlerwoaroof/js-generic-functions.svg?style=svg]]

** What is this?


An implementation of generic functions based on CLOS and the protocols
defined in the Art of the Metaobject protocol, adapted for JS.  These
adaptations include using the prototype chain instead of classes and
additionally providing extensible specializers (as in
https://github.com/sbcl/specializable). For the moment, this is only
used to provide a Shape specializer, as the details of the interaction
between such specializers and subtyping are an open question.

** Docs

*** Basic Usage

#+NAME: imports
#+BEGIN_SRC js
import { defgeneric } from "./genfuns.js";
#+END_SRC

Defining a function works by calling src_js{defgeneric} with some
information about the function name and arguments. Methods are then
added by calling the appropriate methods with a pair of arguments: a
list of specializers (prototypes in the simple case, although there
are other options) and a function to run if those specializers match.

#+NAME: basic-definition
#+BEGIN_SRC js
  const example1generic = defgeneric("example1", "a", "b")
    .primary([Number, Object], (n, __) => [1, n])
    .primary([Object, Number], (_, n) => [2, n])
    .primary([Object, Object], (_, __) => [5, null]);
#+END_SRC

After a generic function has been defined, you can get the function to
call it by accessing its src_js{.fn} attribute.

#+NAME: call-the-function
#+BEGIN_SRC js
  const example1 = example1generic.fn;

  expect(example1(5, {})).toEqual([1, 5]);
  expect(example1({}, 6)).toEqual([2, 6]);
  expect(example1("hello", {})).toEqual([5, null]);
  expect(example1({}, "world")).toEqual([5, null]);
  expect(example1({}, {})).toEqual([5, null]);
#+END_SRC

If a separate reference to the generic function object is maintained,
you can add methods like so:

#+NAME: add-methods
#+BEGIN_SRC js
  example1generic
    .primary([String, Object], (s, __) => [3, s])
    .primary([Object, String], (_, s) => [4, s]);

  expect(example1("hello", {})).toEqual([3, "hello"]);
  expect(example1({}, "world")).toEqual([4, "world"]);
#+END_SRC

*** Other sorts of specializers
#+NAME: specializer-import
#+BEGIN_SRC js
  import { Shape, Eql } from "./genfuns.js";
#+END_SRC



#+NAME: specializer-examples
#+BEGIN_SRC js
  const example2 = defgeneric("example2", "inp")
    .primary([Shape("a", "b")], inp => `a: ${inp.a} b: ${inp.b}`)
    .primary([Shape("a")], inp => `a: ${inp.a} b: <missing>`)
    .primary([Shape(["c", 1])], inp => `c: one`)
    .primary([Shape(["c", 2])], inp => `c: two`)
    .primary([Eql(1)], inp => "one").fn;

  expect(example2({ a: 3, q: "whatever" })).toEqual("a: 3 b: <missing>");
  expect(example2({ a: 3, b: 4, q: "whatever" })).toEqual("a: 3 b: 4");
  expect(example2({ c: 1, q: "whatever" })).toEqual("c: one");
  expect(example2({ c: 2, q: "whatever" })).toEqual("c: two");
  expect(example2(1)).toEqual("one");
#+END_SRC

#+BEGIN_SRC js :tangle src/doc.test.js :comments noweb :noweb tangle :exports none
  <<imports>>
  <<specializer-import>>

  describe("defgeneric", () => {
    test("methods get called appropriately", () => {
      <<basic-definition>>

      <<call-the-function>>

      <<add-methods>>

      <<sample1>>
    });
    test ('specializers work as expected', () => {
      <<specializer-examples>>
    })
  });
#+END_SRC