Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Clojure Design
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
Colour
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Undo
Redo
Keyboard Shortcuts Help
<h2><code>defrecord</code> and <code>deftype</code> improvements for Clojure 1.3</h2> <h3>Motivation</h3> <p>The Java unification of records prevents them from being first class, in either the data or fn sense:</p> <ul> <li>record data is not first class <ul> <li>can't read/write them <ul> <li>crummy choice: maps are good as data, need records for protocol polymorphism</li> </ul> </li> <li>user code cannot fix this <ul> <li>anything that requires EvalRead is not a fix</li> </ul> </li> </ul> </li> <li>record creation is not first class <ul> <li>no per-record factory fn (or access to any associated fn plumbing, e.g. apply)</li> <li>Clojure level use/require doesn't get you access to records</li> <li>user code can mostly fix this (defrecord+factory macro)</li> </ul> </li> <li>Symmetrically, PO Java classes are also not first class <ul> <li>A unified reader form would be ideal</li> </ul> </li> <li>Reduced import complexities</li> </ul> <h3>Solutions</h3> <p><strong>For the sake of discussion, focus will revolve around example defrecords and deftypes defined as</strong></p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (ns myns) (defrecord MyRecord [a b]) (deftype MyType [a b]) </pre></td></tr></table> <h4>Semantics of records as first class data</h4> <p>The semantics of record reader forms and record factory functions are defined as follows:</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (-> (MyRecord. <initialization value> <initialization value>) (into {:a 1, :b 2}) <validation>) </pre></td></tr></table> <p><em>note: The semantics illustrated above should not be taken as implementation detail. at the moment <validation> is undefined and should be considered a no-op.</em></p> <p>The <code><initialization value></code> refers to the same default default values for Java primitive types (as defined by type hinting on the record fields) or <code>nil</code> for instances. For record reader forms, the keys and values must remain as constants as their semantics require that the readable form coincide with the evalable form.</p> <h4>Record and Type reader forms</h4> <p>There would be two additional reader forms added to Clojure.</p> <h5>Labelled record reader form</h5> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> #myns.MyRecord{:a 1, :b 2} </pre></td></tr></table> <h5>Positional record and type reader forms</h5> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> #myns.MyRecord[1 2] </pre></td></tr></table> <p>and</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> #myns.MyType[1 2] </pre></td></tr></table> <p>This syntax satisfies the need for a general-purpose Java class construction reader form. However, not all Java classes are considered fully constructed after the use of their constructors. Therefore, serialization support is not provided for any Java classes by default. For instances such as these, Clojure will continue to provide facilities via <code>print-dup</code> in the known ways.</p> <h4>Generated factory functions</h4> <p>When defining a new defrecord, two functions will also be defined in the same namespace as the record itself. For new deftypes, only the positional constructor outlined below is generated.</p> <h5>Factory function taking a map (<code>defrecord</code> only)</h5> <p>A factory function named <code>map->MyRecord</code> taking a map is defined by <code>defrecord</code>.</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (myns/map->MyRecord {:a 1, :b 2}) ;=> #myns.MyRecord{:a 1, :b 2} </pre></td></tr></table> <h5>Factory function taking positional values (<code>defrecord</code> and <code>deftype</code>)</h5> <p>A factory function named <code>->MyRecord</code> taking positional values (as defined by the record ctor) is also defined by <code>defrecord</code>.</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (myns/->MyRecord 1 2) ;=> #myns.MyRecord{:a 1, :b 2} </pre></td></tr></table> <p>and</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (myns/->MyType 1 2) ;=> #<MyType myns.MyType@2ed277f2> </pre></td></tr></table> <h4>Writing records</h4> <p>When writing record data for the purposes of serialization, the positional reader form is used by default:</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (binding [*print-dup* true] (pr-str (MyRecord. 1 2))) ;=> "#myns.MyRecord[1, 2]" </pre></td></tr></table> <p>However, if you wish to use the map reader form instead, then the following would work:</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (binding [*print-dup* true *verbose-defrecords* true] (pr-str (MyRecord. 1 2))) ;=> "#myns.MyRecord{:a 1, :b 2}" </pre></td></tr></table> <p><em>note: printing forms for types are not provided by default</em></p> <h4>Tool support</h4> <p>Defining Clojure defrecords will also expose static class methods useable at the Java API level. These methods are not documented with the intention of public consumption and are considered implementation details.</p> <h5>Static factory for defrecords</h5> <p>The static factory exposed will mirror the <code>map->MyRecord</code> function:</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (MyRecord/create aMap) </pre></td></tr></table> <h5>Basis access</h5> <p>A static factory allowing access to the basis keys will also be provided:</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (MyRecord/getBasis) ;=> [a b] </pre></td></tr></table> <p>and</p> <table class="wysiwyg-macro" data-macro-name="noformat" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e25vZm9ybWF0fQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> (MyType/getBasis) ;=> [a b] </pre></td></tr></table> <p>The <code>getBasis</code> method will return a <code>PersistentVector</code> of <code>Symbols</code> with (potentially) attached metadata for each field.</p> <h2>Old Ideas</h2> <h3>Lesser Problems:</h3> <ul> <li>generic factory fn <ul> <li>like factory fn, but generic with name</li> <li>introduces weak-referencing, modularity issues, etc.</li> <li>don't have a good problem statement, so ignoring this for now</li> <li>which comes first: generic or specific?</li> </ul> </li> <li>support for common creation patterns <ul> <li>named arguments <ul> <li>with more than a few slots, record construction is difficult to read</li> </ul> </li> <li>default values <ul> <li>maybe needs to be a property of factory fn, not record</li> <li>different factory fns can have different defaults</li> </ul> </li> <li>validations</li> <li>are the patterns truly common?</li> <li>very solvable in user space, esp. if per-record factory fn available</li> </ul> </li> <li>application code needing to know record fields <ul> <li>synthesizing data</li> <li>creating factory fns if we don't provide them</li> </ul> </li> </ul> <h3>Challenges:</h3> <ul> <li>how evaluative should record read/write be? <ul> <li>option 1: records are data++: no EvalReader needed, no non-data semantics</li> <li>option 2: records are more: <ul> <li>maybe EvalReader required?</li> <li>maybe special eval loopholes for constructor fns?</li> </ul> </li> <li>option 1 wins</li> </ul> </li> <li>what happens when readers and writers disagree about a record's fields? <ul> <li>positional approach would either fail or silently do the wrong thing</li> <li>k/v approach lets you get back to the data <ul> <li>still on you to fix it</li> </ul> </li> </ul> </li> <li>does this have be a breaking change? <ul> <li>data print/read: no</li> <li>constructor fn: yes <ul> <li>any good generated name likely to collide with what people are using</li> </ul> </li> </ul> </li> <li>what if defrecord is not present on the read side? <ul> <li>fail?</li> <li>create a plan map instead <ul> <li>plus tag in data?</li> <li>plus tag in metadata?</li> <li>reify in a tagging interface</li> </ul> </li> <li>attempt to load <ul> <li>no – could lead to arbitrary code injection during read</li> </ul> </li> </ul> </li> </ul> <h3>Some Options:</h3> <ul> <li>create reader/writer positional syntax, no constructor fn <ul> <li>pros <ul> <li>easy to deliver efficiently</li> <li>non-breaking</li> <li>introduces no logic (user or clojure) into print/read</li> </ul> </li> <li>cons <ul> <li>what happens if defrecord field count changes?</li> <li>what happens if field names change? <ul> <li>no way to know</li> </ul> </li> </ul> </li> <li>feels like a non-starter</li> </ul> </li> <li>create reader/writer kv syntax, no constructor fn <ul> <li>pros <ul> <li>non-breaking</li> <li>introduces no logic (user or Clojure) into print read</li> <li>can still recover data if defrecord structure has changed</li> </ul> </li> <li>cons <ul> <li>how to deliver read efficiently? <ul> <li>create empty object + merge <ul> <li>cache the empty object we merge against?</li> </ul> </li> <li>reflect against object and manufacture reader fn <ul> <li>who keeps track of this?</li> <li>how would this interact with constructor, if we add that separately?</li> </ul> </li> <li>add a map-based constructor to defrecord classes <ul> <li>what would its signature be?</li> </ul> </li> <li>add a static map based factory fn to defrecord classes</li> </ul> </li> </ul> </li> </ul> </li> <li>reader/writer syntax that depends on a new factory fn <ul> <li>pros <ul> <li>can be efficient</li> <li>can implement any policy in handling defrecord changes</li> </ul> </li> <li>cons <ul> <li>likely breaking (what will the fn names be?)</li> <li>read/write now depends on fns</li> </ul> </li> </ul> </li> <li>positional constructor fn <ul> <li>no</li> <li>replicates the weakness of existing constructors</li> </ul> </li> <li>kv constructor fn <ul> <li>open questions <ul> <li>autogenerated for all defrecords?</li> <li>optional?</li> <li>conveniences (defaults, etc.) <ul> <li>no</li> </ul> </li> </ul> </li> </ul> </li> </ul> <h3>Tentative Proposal 1:</h3> <p>Define a k/v syntax for read and write that does not require a factory fn.</p> <ul> <li>adopt the existing print syntax as legal read syntax? <ul> <li><code>"#:user.P{:x 1, :y 2}"</code></li> </ul> </li> <li>get Rich's input on efficient reader approach (4 possibilities listed above)</li> <li>if reader defrecord fields are different, merge and move on</li> <li>Undecided: if record class not loaded: <ul> <li>TBD: error or make a plain ol map?</li> <li>hm, could fix on writer side: option to dumb records down to maps?</li> </ul> </li> </ul> <h3>Tentative Proposal 2:</h3> <p>Autogenerate a k/v factory fn for all defrecords.</p> <ul> <li><code>(new-foo :x 1 :y 2)</code></li> <li>class constructor is an interop detail</li> <li>factory fn is the Clojure way</li> <li>people can build their own defaults, validation, etc. easily with macros, given this</li> </ul> <h3>Some history:</h3> <p>The <a href="http://dev.clojure.org/jira/browse/CLJ-370">record multimethod</a> was almost ready to go when Rich raised the GC issue. What happens when somebody creates a ton of record classes over time? GC can collect records that are not longer in use, but doesn't clean up the old multimethod functions.</p> <h3>Additional Reading</h3> <p>Some (non-contributed) code that demonstrates people's need for this:</p> <ul> <li><a href="http://cemerick.com/2010/08/02/defrecord-slot-defaults/">cemerick's defrecord slot defaults</a></li> <li>David McNeil's <a href="http://david-mcneil.com/post/765563763/enhanced-clojure-records">enhanced clojure records</a> </li> </ul>
Attachments
Labels
Location
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced