Nov 6, 2009

Case Classes in Ruby

After working a bit in Scala and using the case classes and pattern matching, I've found it's pretty nice to have and it would go well in Ruby! So I've whipped up a little something basic, you can check out the Git repo here: http://github.com/robbrit/ruby_case_classes

What are case classes? They're basically simple classes that don't really do much other than store a set of values (possibly, they don't even need to do that). They're good on their own if you have very basic classes that you want to use, for example exceptions.

With my code, you define a case class like this:
case_class :ClassName, BaseClass, <parameters>
The parameters you pass at the end are the fields of the class and when you instantiate the object you can set them:
case_class :Hello, Object, :title
...
h = Hello.new(:title => "some title")
h.title # => "some title"
Unfortunately at the moment you have to specify the name of the field.

Case classes are handy on their own to save a few keystrokes, but where they help a lot more is with pattern matching. Pattern matching comes from functional programming and was adopted by Scala. It is a handy feature to have, so that's why it is here.

To do pattern matching:
case_class :Blah, Object, :title
case_class :Blih, Object, :title
...
blah = Blah.new(:title => "hello")

match blah do
# Type matching
for_case Blih do
...
end

# Guard and type matching
for_case Blah, :title => "hello" do
...
end

# Just guard matching
for_case "_", :title => "hello" do
...
end

# Multiple guards, one of which is a method call
for_case "_", :title => "hello", :to_i => 0 do
...
end

# Match anything - you could put "_" here, but it's optional
for_case do
...
end
end
Here we have four examples of things you can match against. You can match against just a type, you can match against a type with a guard, you can match against just a guard, and you can have a "default" case if it doesn't match anything.
Guards are basic equality conditions that you can use to match against. If the object fits the guards, then the block is executed.
Note there is a fall-through here. The case that is executed is the first one to match the object, later ones that match are not executed.

Some possible improvements:
- match against values instead of just types. For example:
for_case [] do ...
- inequalities instead of strict equalities in guards
- case class new() doesn't need named parameters
Any other suggestions are welcome.

There's one bug (that I know of). It doesn't work at the top-level. So if this is your entire file, it will fail:
include CaseClasses

case_class :Blah, Object
You need to wrap it in some object. I'm probably having a brain-fart or something as to how to fix this, but for now you'll have this limitation. If anybody has a solution around that, feel free to let me know.

No comments: