NamedValueClass¶ ↑
Quickly add customizable class delegate constants which output their names, not their values. This may be desirable for some DSLs.
Please ask questions or scold me about this here.
Features¶ ↑
-
Creates a class for constants that delegate most work to the stable and efficient ruby core classes.
-
#to_s and #inspect output is the constant’s name and not it’s value.
-
Allows for lowercase constant names.
-
Generates helper methods to access constants based on the parameters set when creating the constants.
-
Provides a mapping from value back to constant.
-
Provides helpers for defining special math/operator rules.
-
Only dependency is the ‘delegate’ library which is already in the ruby stdlib so… really no dependancies.
Installation¶ ↑
gem install named_value_class
Requirements¶ ↑
Requires ruby 1.9. Tested with:
-
ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin10.8.0]
-
jruby 1.6.4 (ruby-1.9.2-p136) (2011-08-23 17ea768) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_29) [darwin-x86_64-java]
Usage¶ ↑
First a complete example:
require 'named_value_class' NamedValueClass Pitch:Fixnum, constrain:0..11 do minus_a :Interval do |lhs,minus,rhs| result = minus.call(rhs) % 12 is_sharp? ? Pitch.sharps(result) : Pitch.flats(result) end all_operators_with_a :Pitch, raise:SyntaxError def as_flat self.class.naturals[self] || self.class.flats[self] end def as_sharp self.class.naturals[self] || self.class.sharps[self] end end Pitch Bs: 0, sharp:true Pitch C: 0, natural:true Pitch Cs: 1, sharp:true Pitch Db: 1, flat:true Pitch D: 2, natural:true Pitch Ds: 3, sharp:true Pitch Eb: 3, flat:true Pitch E: 4, natural:true Pitch Fb: 4, flat:true Pitch Es: 5, sharp:true Pitch F: 5, natural:true Pitch Fs: 6, sharp:true Pitch Gb: 6, flat:true Pitch G: 7, natural:true Pitch Gs: 8, sharp:true Pitch Ab: 8, flat:true Pitch A: 9, natural:true Pitch As: 10, sharp:true Pitch Bb: 10, flat:true Pitch B: 11, natural:true Pitch Cb: 11, flat:true
What does this do?
First create a new class named Pitch that delegates most work to Fixnum with a value between 0 and 11 inclusive; Open a block to define some specialized methods:
NamedValueClass Pitch:Fixnum, constrain:0..11 do
Define how to handle the “Pitch - Interval” use case:
minus_a :Interval do |lhs,minus,rhs|
When defining operators the blocks are always yieled: the LHS (left hand side), a proc with the original, default implementation of the operator you are redefining, and the RHS. The passed proc’s receiver is the LHS so in the following code “minus.call(rhs)” performs “lhs - rhs”.
result = minus(rhs) % 12
Since we plan on setting boolean parameters named ‘sharp’ and ‘flat’ when we define Pitch constants later we can use methods is_sharp?, is_flat?, Pitch.sharps(value) and Pitch.flats(value):
is_sharp? ? Pitch.sharps(result) : Pitch.flats(result) end
It doesn’t make any sense to do math with a Pitch on the left and right sides so all operators should raise a SyntaxError. Examples: Ds + C, Eb / A
all_operators_with_a :Pitch, raise:SyntaxError
Define some instance methods:
def as_flat self.class.naturals[self] || self.class.flats[self] end def as_sharp self.class.naturals[self] || self.class.sharps[self] end end
Now create Pitch constants:
Pitch Bs: 0, sharp:true Pitch C: 0, natural:true Pitch Cs: 1, sharp:true Pitch Db: 1, flat:true Pitch D: 2, natural:true Pitch Ds: 3, sharp:true Pitch Eb: 3, flat:true Pitch E: 4, natural:true Pitch Fb: 4, flat:true Pitch Es: 5, sharp:true Pitch F: 5, natural:true Pitch Fs: 6, sharp:true Pitch Gb: 6, flat:true Pitch G: 7, natural:true Pitch Gs: 8, sharp:true Pitch Ab: 8, flat:true Pitch A: 9, natural:true Pitch As: 10, sharp:true Pitch Bb: 10, flat:true Pitch B: 11, natural:true Pitch Cb: 11, flat:true
An irb session with the above code loaded:
~/d/m/n/examples irb >> load "./pitch_class.rb" #=> true >> include Pitch::NamedValues #=> Object >> B #=> B >> C #=> C >> C.value #=> 0 >> Eb.value #=> 3 >> C + 2 #=> D >> Eb - 4 #=> Bs >> [C,Eb] #=> [C, Eb] >> C > Eb #=> false >> Pitch.flats #=> {1=>Db, 3=>Eb, 4=>Fb, 6=>Gb, 8=>Ab, 10=>Bb, 11=>Cb} >> Pitch.sharps #=> {0=>Bs, 1=>Cs, 3=>Ds, 5=>Es, 6=>Fs, 8=>Gs, 10=>As} >> C.is_sharp? #=> nil >> C.is_flat? #=> nil >> C.is_natural? #=> true >> Pitch H:0, insane:true #=> H >> H #=> H >> C.is_insane? #=> nil >> H.is_insane? #=> true >> Pitch.insanes #=> {0=>H}
Thoughts¶ ↑
I’m still not settled on a best practice for coercion, so it remains missing. from the helpers and specs. You can of course just write a coerce method for any named value class you create.
This gem was created out of a re-factor of my music theory calculator gem, Peas. It greatly reduced the code clutter of that gem. I’d say it reduced the number lines by half at least.
Issues¶ ↑
-
If you run into this error: “super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later” you should know the ruby is producing this message, not me. Try declaring instances directly after the NamedValueClass call for each new type. I’m still trying to accurately repoduce this problem in specs and a solution evades me currently. This is crazy annoying and makes the gem far less useful at the moment. I declare that it will be solved before v1.0. Free beer for whoever can finish either of the above tasks before me.
Author¶ ↑
-
Michael Garriss <mgarriss at gmail.com>
License¶ ↑
Apache 2.0, See the file LICENSE
Copyright © 2011 Michael Garriss