Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
"Playing with Constants, Methods, and Superclasses"
- Loading branch information
Showing
5 changed files
with
197 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
100 changes: 100 additions & 0 deletions
100
_posts/2015-03-24-playing-with-constants-methods-and-superclasses.md
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
--- | ||
layout: post | ||
title: Playing with Constants, Methods, and Superclasses | ||
categories: | ||
- Tips & Tricks | ||
author: Jamis | ||
comments: true | ||
summary: > | ||
A few curious Rubyisms of dubious use, which may yet be worth | ||
knowing about | ||
--- | ||
|
||
File this one under "Ruby Tricks of Questionable Usefulness." Still, it ought to be admitted that even questionably-useful tricks can sometimes inspire unexpectedly-creative solutions. To that end, I present the following, which was inspired by something that Why the Lucky Stiff wrote years ago. (Alas, I can't remember the exact reference---possibly you do. If so, leave a comment, below!) | ||
|
||
Let's begin by observing the following (possibly unexpected) feature of Ruby. Did you know that you can have a constant and a method with the exact same name, and they won't collide? | ||
|
||
Check this out. | ||
|
||
{% highlight ruby %} | ||
Sum = 0 | ||
|
||
def Sum(*args) | ||
args.inject(Sum) { |s,i| s + i } | ||
end | ||
|
||
p Sum #-> 0 | ||
p Sum(1) #-> 1 | ||
p Sum(1,2) #-> 3 | ||
{% endhighlight %} | ||
|
||
See that? The first time we display `Sum`, we're actually printing the value of the _constant_. But as soon as we start adding arguments to the call, that's when we start hitting the method. | ||
|
||
No errors, no warnings, no symbols shadowing other symbols. Just crazy Ruby fun. | ||
|
||
The next (seemingly unrelated) tidbit is this: did you know that when you define a class, the bit where you specify the superclass is actually an _expression_? We usually just put a constant there, but it can be anything (as long as it evaluates to a class). | ||
|
||
{% highlight ruby %} | ||
class Shape | ||
end | ||
|
||
circle_is_shape = true | ||
class Circle < (circle_is_shape ? Shape : Object) | ||
end | ||
|
||
p Circle.superclass #-> Shape | ||
{% endhighlight %} | ||
|
||
I know what you're thinking. "Whoa, that's pretty cool! But why would anyone ever want to do that?" | ||
|
||
I'm glad you asked, because that segues neatly into the third little Ruby trick of the day. | ||
|
||
You probably already knew that defining a class is the same as creating a new `Class` object and assigning it to a constant, right? | ||
|
||
{% highlight ruby %} | ||
# This: | ||
class Shape | ||
end | ||
|
||
# is the same as this: | ||
Shape = Class.new | ||
{% endhighlight %} | ||
|
||
This means that our class names are just constants...and we've already seen that we can have methods that share those same names. Further, we've also seen that the superclass expression in a class definition can be any expression at all... _We can stick a method invocation in there!_ | ||
|
||
Behold: | ||
|
||
{% highlight ruby %} | ||
class Shape | ||
end | ||
|
||
def Shape(which) | ||
require "shapes/#{which}" | ||
Shape.const_get(which.to_s.capitalize) | ||
end | ||
|
||
class Square < Shape | ||
end | ||
|
||
class Circle < Shape(:ellipse) | ||
end | ||
{% endhighlight %} | ||
|
||
So, we have `Shape` declared as both a constant (our base `Shape` class) _and_ a method, where the method just does some work to load a hypothetical class from a file derived from the parameter. | ||
|
||
Then, you can see that we can declare subclasses of `Shape` as normal, with the `Square` class being a typical example. However, we can get tricky. See that? `Circle` is a subclass of _whatever is returned by our `Shape` method_. | ||
|
||
So what? Well, for one thing, it means you can do crazy things like data-driven class hierarchies, where an external configuration file lets you specify (for instance) the type of spline to be used, or the orientation and number of points on a star: | ||
|
||
{% highlight ruby %} | ||
require 'yaml' | ||
config = YAML.load_file('definitions.yml') | ||
|
||
class Spline < Shape(config[:spline]) | ||
end | ||
|
||
class Star < Shape(config[:star]) | ||
end | ||
{% endhighlight %} | ||
|
||
As I said at the beginning, though, this may not have any real practical use. There are certainly other (possibly less-obscure) ways to accomplish this same thing. Still, you have to admit, it's rather fun to think about! |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
.highlight { | ||
padding: 4pt; | ||
} | ||
|
||
.highlight * { | ||
font-size: 12pt; | ||
font-family: "Courier New", Courier, monospace; | ||
} | ||
|
||
.highlight pre { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.highlight > pre > code { | ||
display: block; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.highlight .lineno { | ||
color: #777; | ||
padding-left: 2pt; | ||
padding-right: 4pt; | ||
background: #eee; | ||
} | ||
|
||
.highlight .hll { background-color: #ffffcc } | ||
.highlight { background: #f8f8f8; } | ||
.highlight .c { color: #408080; font-style: italic } /* Comment */ | ||
.highlight .err { border: 1px solid #FF0000 } /* Error */ | ||
.highlight .k { color: #008000; font-weight: bold } /* Keyword */ | ||
.highlight .o { color: #666666 } /* Operator */ | ||
.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ | ||
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ | ||
.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ | ||
.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ | ||
.highlight .gd { color: #A00000 } /* Generic.Deleted */ | ||
.highlight .ge { font-style: italic } /* Generic.Emph */ | ||
.highlight .gr { color: #FF0000 } /* Generic.Error */ | ||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ | ||
.highlight .gi { color: #00A000 } /* Generic.Inserted */ | ||
.highlight .go { color: #808080 } /* Generic.Output */ | ||
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ | ||
.highlight .gs { font-weight: bold } /* Generic.Strong */ | ||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ | ||
.highlight .gt { color: #0040D0 } /* Generic.Traceback */ | ||
.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ | ||
.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ | ||
.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ | ||
.highlight .kp { color: #008000 } /* Keyword.Pseudo */ | ||
.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ | ||
.highlight .kt { color: #B00040 } /* Keyword.Type */ | ||
.highlight .m { color: #666666 } /* Literal.Number */ | ||
.highlight .s { color: #BA2121 } /* Literal.String */ | ||
.highlight .na { color: #7D9029 } /* Name.Attribute */ | ||
.highlight .nb { color: #008000 } /* Name.Builtin */ | ||
.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ | ||
.highlight .no { color: #880000 } /* Name.Constant */ | ||
.highlight .nd { color: #AA22FF } /* Name.Decorator */ | ||
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ | ||
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ | ||
.highlight .nf { color: #0000FF } /* Name.Function */ | ||
.highlight .nl { color: #A0A000 } /* Name.Label */ | ||
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ | ||
.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ | ||
.highlight .nv { color: #19177C } /* Name.Variable */ | ||
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ | ||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */ | ||
.highlight .mf { color: #666666 } /* Literal.Number.Float */ | ||
.highlight .mh { color: #666666 } /* Literal.Number.Hex */ | ||
.highlight .mi { color: #666666 } /* Literal.Number.Integer */ | ||
.highlight .mo { color: #666666 } /* Literal.Number.Oct */ | ||
.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ | ||
.highlight .sc { color: #BA2121 } /* Literal.String.Char */ | ||
.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ | ||
.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ | ||
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ | ||
.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ | ||
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ | ||
.highlight .sx { color: #008000 } /* Literal.String.Other */ | ||
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ | ||
.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ | ||
.highlight .ss { color: #19177C } /* Literal.String.Symbol */ | ||
.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ | ||
.highlight .vc { color: #19177C } /* Name.Variable.Class */ | ||
.highlight .vg { color: #19177C } /* Name.Variable.Global */ | ||
.highlight .vi { color: #19177C } /* Name.Variable.Instance */ | ||
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ |