Skip to content

Instantly share code, notes, and snippets.

@ConradIrwin
Created November 13, 2010 19:56
Show Gist options
  • Select an option

  • Save ConradIrwin/675586 to your computer and use it in GitHub Desktop.

Select an option

Save ConradIrwin/675586 to your computer and use it in GitHub Desktop.
require 'blankslate'
# The Ampex library provides a metavariable X that can be used in conjunction
# with the unary ampersand to create anonymous blocks in a slightly more
# readable way than the default. It was inspired by the clever `Symbol#to_proc`
# method which handles the most common case very elegantly, and discussion with
# Sam Stokes who implemented an earlier version of the idea.
#
# At its simplest, &X can be used as a drop-in replacement for
# `Symbol#to_proc`:
#
# [1,2,3].map &X.to_s
# # => ["1", "2", "3"]
#
# However the real strength in the library comes from allowing you to call
# methods:
#
# [1,"2",3].select &X.is_a?(String)
# # => ["2"]
#
# And, as everything in ruby is a method, create readable expressions without
# the noise of a one-argument block:
#
# [{1 => 2}, {1 => 3}].map &X[1]
# # => [2, 3]
#
# [1,2,3].map &-X
# # => [-1, -2, -3]
#
# ["a", "b", "c"].map &(X * 2)
# # => ["aa", "bb", "cc"]
#
# As an added bonus, the effect is transitive — you can chain method calls:
#
# [1, 2, 3].map &X.to_f.to_s
# # => ["1.0", "2.0", "3.0"]
#
# There are two things to watch out for:
#
# Firstly, &X can only appear on the left:
#
# [1, 2, 3].map &(X + 1)
# # => [2, 3, 4]
#
# [1, 2, 3].map &(1 + X) # WRONG
# # => TypeError, "coerce must return [x, y]"
#
# [[1],[2]].map &X.concat([2])
# # => [[1, 2], [2, 2]]
#
# [[1],[2]].map &[2].concat(X) # WRONG
# # => TypeError, "Metavariable#to_ary should return Array"
#
# Secondly, the arguments or operands will only be evaluated once, and not
# every time:
#
# i = 0 [1, 2].map &(X + (i += 1)) # WRONG
# # => [2, 3]
#
# i = 0 [1, 2].map{ |x| x + (i += 1) }
# # => [2, 4]
#
# For bug-fixes or enhancements, please contact the author:
# Conrad Irwin <[email protected]>
#
# This library is copyrighted under the MIT license, see LICENSE.MIT.
class Metavariable < BlankSlate
def initialize(parent=nil, &block)
@block = block
@parent = parent
end
def method_missing(name, *args, &block)
Metavariable.new(self) { |x| x.send(name, *args, &block) }
end
def to_proc
lambda do |x|
if @block
x = @parent.to_proc.call(x) if @parent
@block.call x
else
x
end
end
end
end
X = Metavariable.new
@ept
Copy link

ept commented Nov 13, 2010

This is really neat! I've wanted something like this for ages.

What would make it perfect in my opinion, is if you take all those examples from the comment and turn them into RSpecs :)

@ConradIrwin
Copy link
Author

Hrmph where's ruby's doctest? :p Sure — I've packaged it up into a gem and everything, so some specs will make me a real rubyist, I'll push it to rapportive-oss.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment