Skip to content

Instantly share code, notes, and snippets.

@timothypratley
Last active January 1, 2016 14:19
Show Gist options
  • Save timothypratley/8157438 to your computer and use it in GitHub Desktop.
Save timothypratley/8157438 to your computer and use it in GitHub Desktop.
Why functions + data is worth an extra argument in preference to classes
; starting with the example you had https://gist.github.com/Engelberg/8141352
(def rows 3)
(def cols 4)
(def cells (for [row (range rows), col (range cols)] [row col]))
(defn select-cells-by-row [row]
(filter (fn [[r c]] (= row r)) cells))
(defn print-cells-by-row [row]
(doseq [cell (select-cells-by-row row)]
(println cell)))
; You described an alternative approach as
; "you’d need to change every single one of your functions to take an additional input"
; My first point is that the actual impact on the code is small:
(defn grid
([] (grid 3 4))
([rows cols] {:rows rows :cols cols}))
(defn cells [grid]
(for [row (range (grid :rows)), col (range (grid :cols))] [row col]))
(defn select-cells-by-row [grid row]
(filter (fn [[r c]] (= row r)) (cells grid)))
(defn print-cells-by-row [grid row]
(doseq [cell (select-cells-by-row grid row)]
(println cell)))
; If I think of grid as a "class", "methods" would have access to "fields" of an implicit "this" object.
; To be a method I need an object to operate on. Above "this" is "grid".
(print-cells-by-row (grid) 1)
; If this were a real class we would use it like so:
;(.print-cells-by-row (grid.) 1)
; and write it like so:
(gen-class
:name my.grid
:init init
:methods [[print-cells-by-row [int] void]])
;;; blah blah blah
(defn -print-cells-by-row [this row]
(dostuff))
; My second point is that you can make classes.
; I know you said "parameterized namespaces", they sound like classes to me.
; Let's image that we created a macro called defm
; which automatically inserted a "this" argument into function arglists,
; and anywhere in the body looked for undefined symbols, and looked them up in "this".
; We could then write
(defm cells []
(for [row (range rows), col (range cols)] [row col]))
; Being a magical macro let's say that it can resolve functions that are declared with defm and knows to pass them the "this".
; Seems pretty feasible given we could just add some meta data to identify them.
; It seems possible to write a less verbose version of gen-class to allow you to write:
(gen-class-lite :name my.grid)
(defc [rows 3 cols 4])
(defm cells [] (for [row (range rows), col (range cols)] [row col]))
(defm select-cells-by-row [row]
(filter (fn [[r c]] (= row r)) cells))
(defm print-cells-by-row [row]
(doseq [cell (select-cells-by-row row)]
(println cell)))
; which would in turn allow
(.print-cells-by-row (my.grid.) 1)
; This seems like the code you want to write, or at least doesn't contain code you don't want to write.
; Why might this be a bad thing?
; Well we hate state and love the interoperability that sticking to maps/vectors/sets gives us...
; But that seems like something we can preserve if make the gen-class-lite implement an atom holidng a hashmap
; (:state will be an atom holding a hashmap, and the class will deref to that atom, making it look like the same thing)
@(my.grid.) => {:rows 3 :cols 4}
; So actually that doesn't sound so bad?
; gen-class-lite + defm adds a bit more magic
; but we avoid an extra argument to functions.
; Now what to do if I want to set :cols to 5?
; Sounds pretty reasonable?
; Hmmm well I'd like to do that as
(swap! (my.grid.) assoc :cols 5)
; at this point is the result still a my.grid? I hope so.
; Maybe the type info just gets shuffled along somehow.
; But what about this:
(swap! (my.grid.) dissoc :cols)
; If this works and I get out a my.grid I'm still in trouble because I broke it!
; I guess we shouldn't do that... which means its back to getters and setters.
; So my third point is that objects are more prescriptive than built in data structures,
; and I don't see a way around that.
; By definition they are changable (or not) in prescribed ways specific to their implementation.
;; An extra argument for all functions, or classes.
;; My fear is that classes eventually lead to a proliferation of custom state manipulation.
;; The extra argument choice is "forced" on us by a lack of syntactic sugar for classes.
;; But a milder way to think about it is that a preference is expressed for functions and data over classes and objects,
;; and that it is a good preference.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment