385 lines
23 KiB
HTML
385 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>Clojure Guides: Polymorphism in Clojure: Protocols and Multimethods</title>
|
|
|
|
|
|
<meta name="description" content="This guide covers:">
|
|
|
|
<meta property="og:description" content="This guide covers:">
|
|
|
|
<meta property="og:url" content="https://clojure-doc.github.io/articles/language/polymorphism/" />
|
|
<meta property="og:title" content="Polymorphism in Clojure: Protocols and Multimethods" />
|
|
<meta property="og:type" content="article" />
|
|
|
|
<link rel="canonical" href="https://clojure-doc.github.io/articles/language/polymorphism/">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link href="https://fonts.googleapis.com/css?family=Alegreya:400italic,700italic,400,700" rel="stylesheet"
|
|
type="text/css">
|
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
|
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/styles/default.min.css">
|
|
<link href="../../../css/screen.css" rel="stylesheet" type="text/css" />
|
|
</head>
|
|
<body>
|
|
|
|
|
|
<nav class="navbar navbar-default">
|
|
<div class="container">
|
|
<div class="navbar-header">
|
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
|
<span class="sr-only">Toggle navigation</span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
</button>
|
|
<a class="navbar-brand" href="../../../index.html">Clojure Guides</a>
|
|
</div>
|
|
<div id="navbar" class="navbar-collapse collapse">
|
|
<ul class="nav navbar-nav navbar-right">
|
|
<li ><a href="../../../index.html">Home</a></li>
|
|
<li><a href="https://github.com/clojure-doc/clojure-doc.github.io">Contribute</a></li>
|
|
</ul>
|
|
</div><!--/.nav-collapse -->
|
|
</div><!--/.container-fluid -->
|
|
</nav>
|
|
|
|
|
|
<div class="container">
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-lg-9">
|
|
<div id="content">
|
|
|
|
<div id="custom-page">
|
|
<div id="page-header">
|
|
<h2>Polymorphism in Clojure: Protocols and Multimethods</h2>
|
|
</div>
|
|
|
|
<p>This guide covers:</p><ul><li>What are polymorphic functions</li><li>Type-based polymorphism with protocols</li><li>Ad-hoc polymorphism with multimethods</li><li>How to create your own data types that behave like core Clojure data types</li></ul><p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 Unported License</a>
|
|
(including images & stylesheets). The source is available <a href="https://github.com/clojure-doc/clojure-doc.github.io">on Github</a>.</p><h2 id="what-version-of-clojure-does-this-guide-cover">What Version of Clojure Does This Guide Cover?</h2><p>This guide covers Clojure 1.5.</p><h2 id="overview">Overview</h2><p>According to Wikipedia,</p><blockquote><p>In computer science, polymorphism is a programming language feature that allows values of different data types to be handled using a uniform interface.</p></blockquote><p>Polymorphism is not at all unique to object-oriented programming languages. Clojure has excellent support for
|
|
polymorphism.</p><p>For example, when a function can be used on multiple data types or behave differently based on additional argument
|
|
(often called <em>dispatch value</em>), that function is <em>polymorphic</em>. A simple example of such function is a function that
|
|
serializes its input to JSON (or other format).</p><p>Ideally, developers would like to use the same function regardless of the input, and be able to extend
|
|
it to new inputs, without having to change the original source. Inability to do so is known as the <a href="http://en.wikipedia.org/wiki/Expression_problem">Expression Problem</a>.</p><p>In Clojure, there are two approaches to polymorphism:</p><ul><li>Data type-oriented. More efficient (modern JVMs optimize this case very well), less flexible.</li><li>So called "ad-hoc polymorphism" where the exact function implementation is picked at runtime based on a special argument (<em>dispatch value</em>).</li></ul><p>The former is implemented using <em>protocols</em>, a feature first introduced in Clojure 1.2. The latter is available via
|
|
<em>multimethods</em>, a feature that was around in Clojure since the early days.</p><h2 id="type-based-polymorphism-with-protocols">Type-based Polymorphism With Protocols</h2><p>It is common for polymorphic functions to <em>dispatch</em> (pick implementation) on the type of the first argument. For example,
|
|
in Java or Ruby, when calling <code>#toString</code> or <code>#to_s</code> on an object, the exact implementation is located using that object's
|
|
type.</p><p>Because this is a common case and because JVM can optimize this dispatch logic very well, Clojure 1.2 introduced a new
|
|
feature called <em>protocols</em>. Protocols are simply groups of functions. Each of the functions can have different
|
|
implementations for different data types.</p><p>Protocols are defined using the <code>clojure.core/defprotocol</code> special form. The example below defines a protocol for working with URLs and URIs.
|
|
While URLs and URIs are not the same thing, some operations make sense for both:</p><pre><code class="clojure">(defprotocol URLLike
|
|
"Unifies operations on URLs and URIs"
|
|
(^String protocol-of [input] "Returns protocol of given input")
|
|
(^String host-of [input] "Returns host of given input")
|
|
(^String port-of [input] "Returns port of given input")
|
|
(^String user-info-of [input] "Returns user information of given input")
|
|
(^String path-of [input] "Returns path of given input")
|
|
(^String query-of [input] "Returns query string of given input")
|
|
(^String fragment-of [input] "Returns fragment of given input"))
|
|
</code></pre><p><code>clojure.core/defprotocol</code> takes the name of the protocol and one or more lists of
|
|
<strong>function name</strong>, <strong>argument list</strong>, <strong>documentation string</strong>:</p><pre><code class="clojure">(^String protocol-of [input] "Returns protocol of given input")
|
|
(^String host-of [input] "Returns host of given input")
|
|
</code></pre><p>The example above uses return type hints. This makes sense in the example but is not necessary. It could have been written
|
|
it as</p><pre><code class="clojure">(defprotocol URLLike
|
|
"Unifies operations on URLs and URIs"
|
|
(protocol-of [input] "Returns protocol of given input")
|
|
(host-of [input] "Returns hostname of given input")
|
|
(port-of [input] "Returns port of given input")
|
|
(user-info-of [input] "Returns user information (username:password) of given input")
|
|
(path-of [input] "Returns path of given input")
|
|
(query-of [input] "Returns query string of given input")
|
|
(fragment-of [input] "Returns fragment of given input"))
|
|
</code></pre><p>There are 3 ways URIs and URLs are commonly represented on the JVM:</p><ul><li><code>java.net.URI</code> instances</li><li><code>java.net.URL</code> instances</li><li>Strings</li></ul><p>When a new protocol imlementation is added for a type, it is called <strong>extending the protocol</strong>. The most common way to extend
|
|
a protocol is via the <code>clojure.core/extend-protocol</code>:</p><pre><code class="clojure">(import java.net.URI)
|
|
(import java.net.URL)
|
|
|
|
(extend-protocol URLLike
|
|
URI
|
|
(protocol-of [^URI input]
|
|
(when-let [s (.getScheme input)]
|
|
(.toLowerCase s)))
|
|
(host-of [^URI input]
|
|
(-> input .getHost .toLowerCase))
|
|
(port-of [^URI input]
|
|
(.getPort input))
|
|
(user-info-of [^URI input]
|
|
(.getUserInfo input))
|
|
(path-of [^URI input]
|
|
(.getPath input))
|
|
(query-of [^URI input]
|
|
(.getQuery input))
|
|
(fragment-of [^URI input]
|
|
(.getFragment input))
|
|
|
|
URL
|
|
(protocol-of [^URL input]
|
|
(protocol-of (.toURI input)))
|
|
(host-of [^URL input]
|
|
(host-of (.toURI input)))
|
|
(port-of [^URL input]
|
|
(.getPort input))
|
|
(user-info-of [^URL input]
|
|
(.getUserInfo input))
|
|
(path-of [^URL input]
|
|
(.getPath input))
|
|
(query-of [^URL input]
|
|
(.getQuery input))
|
|
(fragment-of [^URL input]
|
|
(.getRef input)))
|
|
</code></pre><p>Protocol functions are used just like regular Clojure functions:</p><pre><code class="clojure">(protocol-of (URI. "https://clojure-doc.github.io")) ;= "http"
|
|
(protocol-of (URL. "https://clojure-doc.github.io")) ;= "http"
|
|
|
|
(path-of (URL. "https://clojure-doc.github.io/articles/content.html")) ;= "/articles/content/"
|
|
(path-of (URI. "https://clojure-doc.github.io/articles/content.html")) ;= "/articles/content/"
|
|
</code></pre><h3 id="using-protocols-from-different-namespaces">Using Protocols From Different Namespaces</h3><p>Protocol functions are required and used the same way as regular protocol functions. Consider a
|
|
namespace that looks like this</p><pre><code class="clojure">(ns superlib.url-like
|
|
(:import [java.net URL URI]))
|
|
|
|
(defprotocol URLLike
|
|
"Unifies operations on URLs and URIs"
|
|
(^String protocol-of [input] "Returns protocol of given input")
|
|
(^String host-of [input] "Returns host of given input")
|
|
(^String port-of [input] "Returns port of given input")
|
|
(^String user-info-of [input] "Returns user information of given input")
|
|
(^String path-of [input] "Returns path of given input")
|
|
(^String query-of [input] "Returns query string of given input")
|
|
(^String fragment-of [input] "Returns fragment of given input"))
|
|
|
|
(extend-protocol URLLike
|
|
URI
|
|
(protocol-of [^URI input]
|
|
(when-let [s (.getScheme input)]
|
|
(.toLowerCase s)))
|
|
(host-of [^URI input]
|
|
(-> input .getHost .toLowerCase))
|
|
(port-of [^URI input]
|
|
(.getPort input))
|
|
(user-info-of [^URI input]
|
|
(.getUserInfo input))
|
|
(path-of [^URI input]
|
|
(.getPath input))
|
|
(query-of [^URI input]
|
|
(.getQuery input))
|
|
(fragment-of [^URI input]
|
|
(.getFragment input))
|
|
|
|
URL
|
|
(protocol-of [^URL input]
|
|
(protocol-of (.toURI input)))
|
|
(host-of [^URL input]
|
|
(host-of (.toURI input)))
|
|
(port-of [^URL input]
|
|
(.getPort input))
|
|
(user-info-of [^URL input]
|
|
(.getUserInfo input))
|
|
(path-of [^URL input]
|
|
(.getPath input))
|
|
(query-of [^URL input]
|
|
(.getQuery input))
|
|
(fragment-of [^URL input]
|
|
(.getRef input)))
|
|
</code></pre><p>To use <code>superlib.url-like/path-of</code> and other functions, you require them as regular functions:</p><pre><code class="clojure">(ns myapp
|
|
(:require [superlib.url-like] :refer [host-of scheme-of]))
|
|
|
|
(host-of (java.net.URI. "https://twitter.com/cnn/"))
|
|
</code></pre><h3 id="extending-protocols-for-core-clojure-data-types">Extending Protocols For Core Clojure Data Types</h3><p>TBD</p><h3 id="protocols-and-custom-data-types">Protocols and Custom Data Types</h3><p>TBD: cover extend-type, extend</p><h3 id="partial-implementation-of-protocols">Partial Implementation of Protocols</h3><p>With protocols, it is possible to only implement certain functions for certain types.</p><h2 id="ad-hoc-polymorphism-with-multimethods">Ad-hoc Polymorphism with Multimethods</h2><h3 id="first-example-shapes">First Example: Shapes</h3><p>Lets start with a simple problem definition. We have 3 shapes: square, circle and triangle, and
|
|
need to provide an polymorphic function that calculates the area of the given shape.</p><p>In total, we need 4 functions:</p><ul><li>A function that calculates area of a square</li><li>A function that calculates area of a circle</li><li>A function that calculates area of a triangle</li><li>A polymorphic function that acts as a "unified frontend" to the functions above</li></ul><p>we will start with the latter and define a <em>multimethod</em> (not related to methods on Java objects or object-oriented programming):</p><pre><code class="klipse-clojure nohighlight">(defmulti area (fn [shape & _]
|
|
shape))
|
|
</code></pre><p>Our multimethod has a name and a <em>dispatch function</em> that takes arguments passed to the multimethod and returns
|
|
a value. The returned value will define what implementation of multimethod is used. In Java or Ruby, method implementation
|
|
is picked by traversing the class hierarchy. With multimethods, the logic can be anything you need. That's why it is
|
|
called <em>ad-hoc polymorphism</em>.</p><p>An alternative way of doing the same thing is to pass <code>clojure.core/first</code> instead of an anonymous function:</p><pre><code class="klipse-clojure nohighlight">(defmulti area first)
|
|
</code></pre><p>Next lets implement our area multimethod for squares:</p><pre><code class="klipse-clojure nohighlight">(defmethod area :square
|
|
[_ side]
|
|
(* side side))
|
|
</code></pre><p>Here <code>defmethod</code> defines a particular implementation of the multimethod <code>area</code>, the one that will be used if dispatch function
|
|
returns <code>:square</code>. Lets try it out. Multimethods are invoked like regular Clojure functions:</p><pre><code class="klipse-clojure nohighlight">(area :square 4)
|
|
;= 16
|
|
</code></pre><p>In this case, we pass dispatch value as the first argument, our dispatch function returns it unmodified and
|
|
that's how the exact implementation is looked up.</p><p>Implementation for circles looks very similar, we choose <code>:circle</code> as a reasonable dispatch value:</p><pre><code class="klipse-clojure nohighlight">(defmethod area :circle
|
|
[_ radius]
|
|
(* radius radius Math/PI))
|
|
|
|
(area :circle 3)
|
|
;= 28.274333882308138
|
|
</code></pre><p>For the record, <code>Math/PI</code> in this example refers to <code>java.lang.Math/PI</code>, a field that stores the value of Pi.</p><p>Finally, an implementation for triangles. Here you can see that exact implementations can take different number of
|
|
arguments. To calculate the area of a triangle, we multiple base by height and divide it by 2:</p><pre><code class="klipse-clojure nohighlight">(defmethod area :triangle
|
|
[_ b h]
|
|
(* 1/2 b h))
|
|
|
|
(area :triangle 3 5)
|
|
;= 15/2
|
|
</code></pre><p>In this example we used <strong>Clojure ratio</strong> data type. We could have used doubles as well.</p><p>Putting it all together:</p><pre><code class="klipse-clojure nohighlight">(defmulti area (fn [shape & _]
|
|
shape))
|
|
|
|
(defmethod area :square
|
|
[_ side]
|
|
(* side side))
|
|
|
|
(defmethod area :circle
|
|
[_ radius]
|
|
(* radius radius Math/PI))
|
|
|
|
(defmethod area :triangle
|
|
[_ b h]
|
|
(* 1/2 b h))
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(area :square 4)
|
|
;= 16
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(area :circle 3)
|
|
;= 28.274333882308138
|
|
</code></pre><pre><code class="klipse-clojure nohighlight">(area :triangle 3 5)
|
|
;= 15/2
|
|
</code></pre><h3 id="second-example-tbd">Second Example: TBD</h3><p>TBD: an example that demonstrates deriving</p><h2 id="how-to-create-custom-data-type-that-core-functions-can-work-with">How To Create Custom Data Type That Core Functions Can Work With</h2><p>TBD: <a href="https://github.com/clojure-doc/clojure-doc.github.io#how-to-contribute">How to Contribute</a></p><h2 id="wrapping-up">Wrapping Up</h2><p>TBD: <a href="https://github.com/clojure-doc/clojure-doc.github.io#how-to-contribute">How to Contribute</a></p>
|
|
|
|
<div id="prev-next">
|
|
|
|
<a href="../macros/index.html">« Clojure Macros and Metaprogramming</a>
|
|
|
|
|
|
||
|
|
|
|
|
|
<a href="../concurrency_and_parallelism/index.html">Concurrency and Parallelism in Clojure »</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div id="sidebar">
|
|
<h3>Links</h3>
|
|
<ul id="links">
|
|
|
|
<li><a href="../../about/index.html">About</a></li>
|
|
|
|
<li><a href="../../content/index.html">Table of Contents</a></li>
|
|
|
|
<li><a href="../../tutorials/getting_started/index.html">Getting Started with Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/introduction/index.html">Introduction to Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/emacs/index.html">Clojure with Emacs</a></li>
|
|
|
|
<li><a href="../../tutorials/vim_fireplace/index.html">Clojure with Vim and fireplace.vim</a></li>
|
|
|
|
<li><a href="../../tutorials/eclipse/index.html">Starting with Eclipse and Counterclockwise For Clojure Development</a></li>
|
|
|
|
<li><a href="../../tutorials/basic_web_development/index.html">Basic Web Development</a></li>
|
|
|
|
<li><a href="../../tutorials/parsing_xml_with_zippers/index.html">Parsing XML in Clojure</a></li>
|
|
|
|
<li><a href="../../tutorials/growing_a_dsl_with_clojure/index.html">Growing a DSL with Clojure</a></li>
|
|
|
|
<li><a href="../core_overview/index.html">Overview of clojure.core, the standard Clojure library</a></li>
|
|
|
|
<li><a href="../namespaces/index.html">Clojure Namespaces and Vars</a></li>
|
|
|
|
<li><a href="../collections_and_sequences/index.html">Collections and Sequences in Clojure</a></li>
|
|
|
|
<li><a href="../functions/index.html">Functions in Clojure</a></li>
|
|
|
|
<li><a href="../laziness/index.html">Laziness in Clojure</a></li>
|
|
|
|
<li><a href="../interop/index.html">Clojure interoperability with Java</a></li>
|
|
|
|
<li><a href="../macros/index.html">Clojure Macros and Metaprogramming</a></li>
|
|
|
|
<li><a href="index.html">Polymorphism in Clojure: Protocols and Multimethods</a></li>
|
|
|
|
<li><a href="../concurrency_and_parallelism/index.html">Concurrency and Parallelism in Clojure</a></li>
|
|
|
|
<li><a href="../glossary/index.html">Clojure Terminology Guide</a></li>
|
|
|
|
<li><a href="../../ecosystem/libraries_directory/index.html">A Directory of Clojure Libraries</a></li>
|
|
|
|
<li><a href="../../ecosystem/libraries_authoring/index.html">Library Development and Distribution</a></li>
|
|
|
|
<li><a href="../../ecosystem/generating_documentation/index.html">Generating Documentation</a></li>
|
|
|
|
<li><a href="../../ecosystem/data_processing/index.html">Data Processing (Help Wanted)</a></li>
|
|
|
|
<li><a href="../../ecosystem/web_development/index.html">Web Development (Overview)</a></li>
|
|
|
|
<li><a href="../../ecosystem/maven/index.html">How to use Maven to build Clojure projects</a></li>
|
|
|
|
<li><a href="../../ecosystem/community/index.html">Clojure Community</a></li>
|
|
|
|
<li><a href="../../ecosystem/user_groups/index.html">Clojure User Groups</a></li>
|
|
|
|
<li><a href="../../ecosystem/running_cljug/index.html">Running a Clojure User Group</a></li>
|
|
|
|
<li><a href="../../ecosystem/books/index.html">Books about Clojure and ClojureScript</a></li>
|
|
|
|
<li><a href="../../cookbooks/data_structures/index.html">Data Structures (Help wanted)</a></li>
|
|
|
|
<li><a href="../../cookbooks/strings/index.html">Strings</a></li>
|
|
|
|
<li><a href="../../cookbooks/math/index.html">Mathematics with Clojure</a></li>
|
|
|
|
<li><a href="../../cookbooks/date_and_time/index.html">Date and Time (Help wanted)</a></li>
|
|
|
|
<li><a href="../../cookbooks/files_and_directories/index.html">Working with Files and Directories in Clojure</a></li>
|
|
|
|
<li><a href="../../cookbooks/middleware/index.html">Middleware in Clojure</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/home.html">java.jdbc - Getting Started</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/using_sql.html">java.jdbc - Manipulating data with SQL</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/using_ddl.html">java.jdbc - Using DDL and Metadata</a></li>
|
|
|
|
<li><a href="../../ecosystem/java_jdbc/reusing_connections.html">java.jdbc - How to reuse database connections</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/home/index.html">core.typed - User Documentation Home</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/user_documentation/index.html">core.typed - User Documentation</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/rationale/index.html">core.typed - Rationale</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/quick_guide.html">core.typed - Quick Guide</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/start/introduction_and_motivation/index.html">core.typed - Getting Started: Introduction and Motivation</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/types/index.html">core.typed - Types</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/start/annotations/index.html">core.typed - Annotations</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/poly_fn/index.html">core.typed - Polymorphic Functions</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/filters/index.html">core.typed - Filters</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/mm_protocol_datatypes/index.html">core.typed - Protocols</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/loops/index.html">core.typed - Looping constructs</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/function_types/index.html">core.typed - Functions</a></li>
|
|
|
|
<li><a href="../../ecosystem/core_typed/limitations/index.html">core.typed - Limitations</a></li>
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<footer>Copyright © 2021 Multiple Authors
|
|
<p style="text-align: center;">Powered by <a href="http://cryogenweb.org">Cryogen</a></p></footer>
|
|
</div>
|
|
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
|
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
|
|
<script src="../../../js/highlight.pack.js" type="application/javascript"></script>
|
|
<script>hljs.initHighlightingOnLoad();</script>
|
|
|
|
<link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/app.klipse.tech/css/codemirror.css">
|
|
<script>
|
|
window.klipse_settings = {
|
|
"selector" : ".klipse-clojure"
|
|
};
|
|
</script>
|
|
<script src="https://storage.googleapis.com/app.klipse.tech/plugin/js/klipse_plugin.js"></script>
|
|
</body>
|
|
</html>
|