[com.sagevisuals/thingy "0"]
com.sagevisuals/thingy {:mvn/version "0"}
(require '[thingy.core :refer [defn-thingy make-thingy]])
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.
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.
There are two steps to using the thingy library: creating an instance, and assigning the invocation function.
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
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.
Eso-lang: A fictitious, esoteric language.
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
.
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
.
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