Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between Record constructor and positional factory function

Tags:

clojure

Suppose I define a record called Node: (defrecord Node [tag attributes children]).

After this definition, according to the docstring of defrecord a factory function called ->Node is defined, as well as another factory function map->Node and a Java class constructor Node..

I'm wondering what exactly the difference is between the positional factory function ->Node and the constructor Node., apart from the normal differences between a Java class constructor / method on the one hand and a clojure function on the other (by normal differences I'm thinking things like the fact that functions are first-class in Clojure while methods are not).

like image 742
jjpe Avatar asked Dec 27 '22 02:12

jjpe


1 Answers

(Update: see end of this answer for a note on primitive field types vs. parameter types of the ctor vs. parameter types of the factory.)

The positional factory just calls the constructor directly. The only interesting thing beyond that is that for records / types with large numbers of fields (namely over 20, which is the maximum number of positional arguments a Clojure function can accept) making the constructor call is slightly more involved (since you have to unpack some arguments from the rest-args seq); positional factories as emitted by defrecord and deftype handle that correctly, and moreover check that the correct number of arguments is supplied, throwing an appropriate exception if not.

This is documented in the docstring for the private function clojure.core/build-positional-factory; say (doc clojure.core/build-positional-factory) at the REPL to read it, or (source clojure.core/build-positional-factory) to see the source.

The end result looks roughly like this:

;; positional factory for a type with up to 20 fields
(defn ->Foo
  "Construct a Foo."
  [x y z]
  (new Foo x y z))

;; positional factory for a type with many fields
(defn ->Bar
  "Construct a Bar."
  [a b c d e f g h i j k l m n o p q r s t & overage]
  (if (= (count overage) 2)
    (new Bar a b c d e f g h i j k l m n o p q r s t
             (nth overage 0) (nth overage 1))
    (throw
     (clojure.lang.ArityException.
      (+ 20 (count overage)) (name '->Bar))))))

A note on parameter types:

Not sure if this falls under the rubric of "normal differences", so I'll mention it explicitly: deftype / defrecord introduced classes may have fields of primitive types, in which case the corresponding parameters of the constructor will also be of primitive types. However, as of Clojure 1.5.1, the positional factories always take all-Object arguments, even if technically they could be declared as primitive-accepting functions (that is, if the primitive types involved are long and/or double and there are at most four positional parameters).

like image 150
Michał Marczyk Avatar answered Apr 25 '23 16:04

Michał Marczyk