Extend IFn protocol like if you were part of clojurescript core team
source link: https://blog.klipse.tech/clojure/2016/04/07/ifn.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Extend IFn protocol like if you were part of clojurescript core team
Apr 7, 2016 • Yehonathan Sharvit
In Clojure, the language itself is modifiable
In clojure
, the developer and the author of the language have a very intimate relationship: almost every part of the language author is extensible and modifiable by the clojure
developer.
It is well known that macros
allow the developer to add new semantics to the language - as if they were an original part of the language.
But what about functions?
Functions as a concept is at the core of clojure
(like for any other functional programming language). So it’s not obvious to imagine that any kind of extensibility will be provided also for the concept of function.
I felt completely enlightened when I discovered - after a couple of months of clojure
programming - that:
In clojurescript, any type of the language could behave like a function
This article will guide you through the path to enlightenment…
Keywords and maps as functions: how does it work?
You probably already know that keywords and maps could be used as functions.
Regular keyword get:
xxxxxxxxxx
(:a {:a "A"})
the evaluation will appear here (soon)...
keyword get - with not-found values like get:
xxxxxxxxxx
(:a {} :n/a)
xxxxxxxxxx
the evaluation will appear here (soon)...
regular map get:
xxxxxxxxxx
({:a "A"} :a)
xxxxxxxxxx
the evaluation will appear here (soon)...
map get - with not-found values like get:
xxxxxxxxxx
({} :a :n/a)
xxxxxxxxxx
the evaluation will appear here (soon)...
Now, let’s see what happens behind the scenes…
We will see that the mechanism that enables keywords and maps to behave like function is completely open and extensible.
Let’s have a look at an excerpt from clojurescript
source code to understand this mechanism.
Don’t be afraid by the big pyramid…
(defprotocol IFn
"Protocol for adding the ability to invoke an object as a function.
For example, a vector can also be used to look up a value:
([1 2 3 4] 1) => 2"
(-invoke
[this]
[this a]
[this a b]
[this a b c]
[this a b c d]
[this a b c d e]
[this a b c d e f]
[this a b c d e f g]
[this a b c d e f g h]
[this a b c d e f g h i]
[this a b c d e f g h i j]
[this a b c d e f g h i j k]
[this a b c d e f g h i j k l]
[this a b c d e f g h i j k l m]
[this a b c d e f g h i j k l m n]
[this a b c d e f g h i j k l m n o]
[this a b c d e f g h i j k l m n o p]
[this a b c d e f g h i j k l m n o p q]
[this a b c d e f g h i j k l m n o p q r]
[this a b c d e f g h i j k l m n o p q r s]
[this a b c d e f g h i j k l m n o p q r s t]
[this a b c d e f g h i j k l m n o p q r s t rest]))
(deftype Keyword [...]
...
IFn
(-invoke [kw coll]
(get coll kw))
(-invoke [kw coll not-found]
(get coll kw not-found))
...
)
(deftype PersistentHashMap [...]
...
IFn
(-invoke [coll k]
(-lookup coll k))
(-invoke [coll k not-found]
(-lookup coll k not-found))
...
)
What an amazing discovery:
clojurescript
exposes its most basic element - the function - as a protocol!!!
Keywords and maps behave like functions simply because they implement the -invoke
method of IFn
protocol.
And you - as a clojurescript
developer - are not limited to the types that implement the IFn
protocol. The reason is that in clojurescript
every protocol can be extended through extend-type
and extend-protocol
after the type
is defined.
We will see now how simple it is to make strings behave like functions.
From this below, we are dealing with hacky stuff. The objective of this article is to illustrate advanced feature of the language and I definitely discourage you to deal with that hacky stuff on production code.
David Nolen stated it this way in a interesting discussion with me on clojurians Slack:
“Extending base types is really about convenience, expressivity - no way at the moment for it to be performant” - David Nolen.
Strings as functions: Home brew
Let’s see how to make strings behave like functions using extend-type
and implementing -invoke
in a similar way that it was done for keywords in clojurescript
source code:
xxxxxxxxxx
(ns my.playground)
(extend-type js/String
IFn
(-invoke
([s coll]
(get coll (str s)))
([s coll not-found]
(get coll (str s) not-found))))
("a" {"a" "A"})
xxxxxxxxxx
the evaluation will appear here (soon)...
And it works also with not-found values like get:
xxxxxxxxxx
("a" {} :n/a)
xxxxxxxxxx
the evaluation will appear here (soon)...
For some technical reason (see CLJS-1618), we have to wrap the string into str
. But beside that, it is really simple.
Also, this extensibility for base types is not available in clojure
. The reason is that in clojure
only types that are defined in clojure
can be extended to java
interfaces. And IFn
is a java
interface and the base types (strings, numbers, regexps, maps,etc..) are defined in java
.
Regular expressions as functions: Home brew
Now, let’s see how to do fancy stuff with regular expressions.
Instead of having to write this:
(re-find re s)
to check if a string matches a regular expression(clojure.string/replace s match replacement)
to do string replacements
We will show what to do in order to be able to write that:
(re s)
to check if a string matches a regular expression(match replacement s)
to do string replacements
Let’s see it in action with KLIPSE:
xxxxxxxxxx
(ns my.regexp
(:require [clojure.string :as string]))
(extend-type js/RegExp
IFn
(-invoke
([match s] (re-find match s))
([match replacement s] (string/replace s match replacement))))
(#"clojure" "clojurescript")
xxxxxxxxxx
the evaluation will appear here (soon)...
And now of the string replacement:
xxxxxxxxxx
(#"clojure" "clojurescript" "clojure rocks")
xxxxxxxxxx
the evaluation will appear here (soon)...
Clojurescript
extensibility allowed us to create new type of functions with a few lines of (simple) code…
Don’t you feel enlightened?
Like I said above, dealing with base types is really dangerous. So, please be careful…
If you have any ideas about implementing IFn
protocol for custom types, I’d really appreciate if you share it in the comments below.
Clojurescript rocks!
to stay up-to-date with the coolest interactive articles around the world.
Discover more cool interactive articles about javascript, clojure[script], python, ruby, scheme, c++ and even brainfuck!
Give Klipse a Github star to express how much you appreciate Code Interactivity.
Subscribe to the Klipse newsletter:Feel free to email me [email protected] for getting practical tips and tricks in writing your first interactive blog post.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK