EDIT June: I’ve followed-up this post: order of operations
NOTE: this post seems to have a ton of code samples, but it’s pretty much just the same one over and over with some small variations to play with an idea.
Suppose you were writing a program to help you write a novel, and you start out with something like this:
class Novel
def initialize(title)
@title = title
@chapters = []
end
end
class Chapter
def initialize(text)
@text = text
end
end
moby_dick = Novel.new("Moby Dick")
first_chapter = Chapter.new("Call me Ishmael...")
After you write this, you pause, because how are you going to define the interface for adding a new chapter to a novel?
Something like this works fine:
class Novel
def initialize(title)
@title = title
@chapters = []
end
def add_chapter(chapter)
chapters << chapter
end
private
attr_reader :chapters
end
class Chapter
def initialize(text)
@text = text
end
end
moby_dick = Novel.new("Moby Dick")
moby_dick.add_chapter Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>
Writing methods is good, and that method has a perfectly adequate name. But some clever ducks will be dissatisfied by it, because they know there’s a more fun, or maybe a more expressive way:
class Novel
def initialize(title)
@title = title
@chapters = []
end
def <<(chapter)
chapters << chapter
end
private
attr_reader :chapters
end
class Chapter
def initialize(text)
@text = text
end
end
moby_dick = Novel.new("Moby Dick")
moby_dick << Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>
The <<
“shovel” operator is familiar to most Ruby
programmers, and chapters are a natural thing to shovel into a novel, so it
feels kind of natural to use it here.
<<
is the idiomatic operator to use, but sometimes I don’t want to be
idiomatic, I want to be weird. Maybe I feel like this version suits me better:
class Novel
def initialize(title)
@title = title
@chapters = []
end
def <=(chapter)
chapters << chapter
end
private
attr_reader :chapters
end
class Chapter
def initialize(text)
@text = text
end
end
moby_dick = Novel.new("Moby Dick")
moby_dick <= Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>
For me, <=
is a more visually stimulating, writerly operator.
I probably shouldn’t do this. It’s a totally weird interface! <=
means “less
than or equal to”, not append..!
In fact, I’m only allowed to even do that because <=
is on a list of
acceptable operators1. You can’t just name your operator whatever. Let’s try:
class Novel
def initialize(title)
@title = title
@chapters = []
end
def ✏️(chapter)
chapters << chapter
end
private
attr_reader :chapters
end
class Chapter
def initialize(text)
@text = text
end
end
moby_dick = Novel.new("Moby Dick")
moby_dick ✏️ Chapter.new("Call me Ishmael...") #=> undefined method `✏️' for main:Object (NoMethodError)
It totally blows up. This works, though:
moby_dick = Novel.new("Moby Dick")
moby_dick.✏️ Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>
Surprisingly, this does too:
moby_dick = Novel.new("Moby Dick")
moby_dick . ✏️ Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>
There are so many spaces on that second line, but it totally works.
It sucks that the period is necessary for this to be syntactically valid. I think. I don’t know what the consequences would be of allowing programmers to define arbitrary operators. Maybe they’re vast?