Liking cljdoc? Tell your friends :D

Setup
API
Changelog
Introduction
Usage
Examples
Glossary
Contact

thingy

A Clojure library that provides a bizarre function definition mechanism

Setup

Leiningen/Boot

[com.sagevisuals/thingy "0"]

Clojure CLI/deps.edn

com.sagevisuals/thingy {:mvn/version "0"}

Require

(require '[thingy.core :refer [defn-thingy make-thingy]])

Introduction

This Clojure library provides tools to create function-like objects which implement the vector interface. It is intended for a very specific, niche use case and it is exceedingly unlikely to be useful or safe outside of that niche.

A thingy instance, when appearing at the head of an S-expression serves as a function, consuming the arguments contained in the tail and yielding a value. Unlike a typical function, the internal structure of a thingy is manipulatable with all the functions that make up the vector interface.

Such objects may be used in academic programming languages that explore using sequential collections of values to define functions. It is certainly possible to write

(application-function vector arg1 arg2...)

where application-function provides some kind of logic about how to interpret vector. But having application-function scattered around is visually distracting. Instead of plainly representing the concepts, the machinery is leaking onto the page. It is much nicer to write

(fn-vec arg1 arg2...)

as well to read and to understand. The thingy library enables such streamlining.

Usage

Basics

Let's pretend we want to create a trio of single-arity functions.

  • Alice returns the string Hello, World! regardless of the argument.
  • Bob increments its argument.
  • Charlie reverses the sequence passed as an argument.

Since we're pretending, we want to define Alice, Bob, and Charlie each as a sequential collection, such as a Clojure vector. For the sake of discussion, we'll state the following "function" definitions, using vector literals.

(def Alice [:a])
(def Bob [:b])
(def Charlie [:c])

Let's write an application function, whose name we'll intentionally mis-spell as appli so that we don't shadow clojure.core/apply. appli dispatches on argument f's first element.

(defn appli
  [f x]
  (condp = (first f)
    :a "Hello, World!"
    :b (inc x)
    :c (reverse x)))

For this demonstration, that condp serves as a kind of crude lookup table, but appli could be anything, such as a higher-order function, or a recursive function, etc.

Now we test out what we've done.

(appli Alice :an-ignored-arg) ;; => "Hello, World!"

(appli Bob 99) ;; => 100

(appli Charlie [:chocolate :strawberry :vanilla]) ;; => (:vanilla :strawberry :chocolate)

That looks promising. Alice returns string Hello, World! regardless of the argument, Bob increments the numeric argument, and Charlie indeed reverses the sequence passed as the next argument.

There's something we'd like to improve: We wanted to think of Alice, Bob, and Charlie as functions, so having appli sprinkled throughout kinda destroys that illusion.

Regular Clojure vectors have the capability to act as functions when at the head of an S-expression.

([97 98 99] 1) ;; => 98

We see that when a vector is at the head of the S-expression, there's an implied nth. It's as if we had written this.

(nth [97 98 99] 1) ;; => 98

For our Alice/Bob/Charlie trio, we'd like for there to be instead an implied appli.

To do that, let's introduce a new utility, make-thingy.

(make-thingy 97 98 99) ;; => [97 98 99]

Does it do vector tasks?

(count (make-thingy 97 98 99)) ;; => 3
(nth (make-thingy 97 98 99) 1) ;; => 98
(conj (make-thingy 97 98 99) 100) ;; => [97 98 99 100]

That certainly looks like a vector. Each instance of a thingy implements the vector interface, so we have all the familiar functions like count, conj, etc.

Now, let's re-define the our trio using make-thingy.

(def Alice (make-thingy :a))
(def Bob (make-thingy :b))
(def Charlie (make-thingy :c))

Let's see what happens when we try to use a thingy as a function.

(Alice 0) ;; => :a

Hmm. It appears to behave like a regular vector, with an implicit nth. This is the default.

Next, we'll re-set the invocation behavior of all instances of thingys to appli using another utility, defn-thingy. It looks pretty much like defn.

(defn-thingy my-appli
  "doc string"
  {:metadata "foo"}
  [f x]
  (appli f x))

defn-thingy mutates the invocation behavior for all thingy instances.

Let's see how our trio behaves.

(Alice :an-ignored-arg) ;; => "Hello, World!"

(Bob 99) ;; => 100

(Charlie [:chocolate :strawberry :vanilla]) ;; => (:vanilla :strawberry :chocolate)

Now our Alice, Bob, and Charlie thingys behave like functions.

Details

There are two steps to using the thingy library: creating an instance, and assigning the invocation function.

  1. Creating and manipulating a thingy instance is analogous to that of vectors. To create, use make-thingy

    (make-thingy 97 98 99) ;; => [97 98 99]

    similar to using clojure.core/vector. (There is no analogous facility to making a vector literal.) Create a new instance from some other existing collection like this.

    (into (make-thingy) #{:foo :bar :baz}) ;; => [:baz :bar :foo]

    Note that the type is distinct from a standard Clojure persistent vector.

    (type (into (make-thingy) #{:foo :bar :baz}))
    ;; => com.sagevisuals.AltFnInvocablePersistentVector

    Manipulate an instance with your favorite tools.

    (update (make-thingy 97 98 99) 2 inc) ;; => [97 98 100]

    (map inc (make-thingy 97 98 99)) ;; => (98 99 100)

    Note that, just like Clojure vectors, sequence functions consuming a thingy instance will return a sequence.

    (type (map inc (make-thingy 97 98 99))) ;; => clojure.lang.LazySeq
  2. Assigning a thingy invocation function is analogous to using defn. One difference is that supplying a doc-string and metadata are required. (Feel free to leave them empty, though.)

    (defn-thingy yippee
      "My docstring."
      {:added 1.2}
      [_ _]
      "Hooray!")

    This example assigns a 2-arity function that returns a string, ignoring its arguments. Observe.

    ((make-thingy) 99) ;; => "Hooray!"
    ((make-thingy :a :b :c) :foo) ;; => "Hooray!"
    ((make-thingy 1.23 4.56) 22/7) ;; => "Hooray!"

    The function is accessible in the same manner as any other var in the namespace.

    The name has no affect on the operation of thingy instances, but is provided so that the function may be invoked manually, like this.

    (yippee :a :b) ;; => "Hooray!"

    Evaluating defn-thingy synchronously mutates the invocation function for all thingy instances.

    The invocation function may have an arity of zero to eight, inclusive. When there is at least one argument, the thingy instance is passed as the first argument.

Examples

Eso-lang: A fictitious, esoteric language.

Glossary

thingy instance

An instance of com.sagevisuals.AltFnInvocableVector. In nearly every regard, identical to an instance of clojure.lang.PersistentVector. Whereas a Clojure vector at the head of an S-expression behaves as if there is an implicit nth, a thingy instance invokes a dynamically-mutable invocation function. (Defaults to nth.)

Instantiated with make-thingy.

invocation function

The function that is invoked when evaluating an S-expression with a thingy instance at the head. In all respects, a standard Clojure function, that may be called from any site.

Defined with defn-thingy.


License

This program and the accompanying materials are made available under the terms of the MIT License.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close