Skip to content

Instantly share code, notes, and snippets.

@lynaghk
Created December 5, 2012 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lynaghk/4210677 to your computer and use it in GitHub Desktop.
Save lynaghk/4210677 to your computer and use it in GitHub Desktop.
Metaprogramming EDN in ruby
#Idiomatic record constructors in Ruby
#My C2PO grammar of graphics uses records (typed maps, basically) to represent pieces of the grammar.
#E.g., in Clojure you can specify a linear scale via a constructor function:
#
# (scale/linear :domain [0 10] :label "The X Axis")
#
#returns the appropriate scale.linear record with the two key/value pairs given above.
#I'm trying to implement the same thing in Ruby; basically just shell records that only know how to serialize themselves out to EDN---no validation of arguments or anything.
#Here's the parent class:
module C2PO
class C2PORecord < Hash
def initialize(h={})
self.merge! h
end
def to_edn
_, prefix, name = self.class.name.split("::").map(&:downcase)
EDN.tagout "com.keminglabs/c2po$#{prefix}$#{name}", {}.merge(self)
end
end
end
#then
module C2PO
module Geom
class Point < C2PORecord
end
end
end
#makes something I can instantiate as such:
include C2PO
p = Geom::Point.new :radius => 2
p.to_edn # => "com.keminglabs/c2po$geom$point {:radius 2}"
#however, the ::, capitalization, and #new feel a bit verbose.
#One alternative is to put constructors on the appropriate modules:
module C2PO
module Geom
class Point < C2PORecord
end
def self.point(h={})
Point.new h
end
end
end
#and then call with the nicer syntax:
p = Geom.point :radius => 2
#but this requires the implementation to repeat itself (saying "point" three times).
#Ruby friends: is there a nice way I can metaprogram my way out of this?
#For reference, here's my solution in Clojure using actual macros (from: https://github.com/keminglabs/c2po-clojure/blob/master/src/clj/c2po/records.clj):
# (ns c2po.records
# (:use [c2po.literals :only [print-literal]])
# (:require [clojure.string :as str]
# clojure.pprint))
# (defmacro def-c2po-record
# [prefix name]
# (let [record-name (str/capitalize (clojure.core/name name))]
# `(do
# (defrecord ~(symbol record-name) [])
# ;;print the record appropriately with Clojure's regular and pretty print systems
# (defmethod print-method ~(symbol record-name) [x# w#]
# (print-literal ~prefix ~(clojure.core/name name) x# w#))
# (defmethod clojure.pprint/simple-dispatch ~(symbol record-name) [x#] (pr x#))
# ;;record constructor
# (defn ~name [& kwargs#]
# (~(symbol (str "map->" record-name))
# (apply hash-map kwargs#))))))
# (ns c2po.geom
# (:use [c2po.records :only [def-c2po-record]]))
# (def-c2po-record "geom" point)
# (def-c2po-record "geom" line)
# (def-c2po-record "geom" bar)
# (def-c2po-record "geom" boxplot)
@lynaghk
Copy link
Author

lynaghk commented Dec 5, 2012

Ping: @crnixon you have any thoughts on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment