emacs.d/clones/lisp/clojure-doc.org/articles/language/polymorphism/index.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 &amp; 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]
(-&gt; 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]
(-&gt; 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 &amp; _]
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 &amp; _]
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">&laquo; Clojure Macros and Metaprogramming</a>
||
<a href="../concurrency_and_parallelism/index.html">Concurrency and Parallelism in Clojure &raquo;</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 &copy; 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>