Last active
January 1, 2016 14:19
-
-
Save timothypratley/8157438 to your computer and use it in GitHub Desktop.
Why functions + data is worth an extra argument in preference to classes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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