Skip to content

Commit

Permalink
"Playing with Constants, Methods, and Superclasses"
Browse files Browse the repository at this point in the history
  • Loading branch information
jamis committed Mar 24, 2015
1 parent b631274 commit 04364f9
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 2 deletions.
4 changes: 2 additions & 2 deletions _config.yml
@@ -1,7 +1,7 @@
name: The Buckblog
description: assorted ramblings by Jamis Buck
markdown: redcarpet
highlighter: true
markdown: kramdown
highlighter: pygments
exclude: [public]
url: http://weblog.jamisbuck.org
permalink: /:year/:i_month/:i_day/:title.html
Expand Down
1 change: 1 addition & 0 deletions _layouts/default.html
Expand Up @@ -6,6 +6,7 @@
<link href="/stylesheets/styles.css?v3" rel="stylesheet" type="text/css" />
<link href="/stylesheets/maze.css?v1" rel="stylesheet" type="text/css" />
<link href="/stylesheets/syntax-legacy.css?v1" rel="stylesheet" type="text/css" />
<link href="/stylesheets/syntax.css?v1" rel="stylesheet" type="text/css" />
<link rel="alternate" type="application/rss+xml" title="The Buckblog Feed" href="http://feeds.feedburner.com/buckblog" />

<script type="text/javascript" src="/javascripts/prototype.js"></script>
Expand Down
5 changes: 5 additions & 0 deletions _plugins/post_filters.rb
Expand Up @@ -3,6 +3,11 @@ def minutes_to_read(input)
words = number_of_words(input)
(words / 250.0).ceil
end

def debug(input)
p input
input
end
end

Liquid::Template.register_filter(PostFilters)
100 changes: 100 additions & 0 deletions _posts/2015-03-24-playing-with-constants-methods-and-superclasses.md
@@ -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!
89 changes: 89 additions & 0 deletions stylesheets/syntax.css
@@ -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 */

0 comments on commit 04364f9

Please sign in to comment.