ampex 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.markdown +18 -1
  2. data/lib/ampex.rb +63 -12
  3. metadata +4 -4
data/README.markdown CHANGED
@@ -1,5 +1,8 @@
1
1
  The Ampex (`&X`) 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 created an earlier version.
2
2
 
3
+ Usage
4
+ -----
5
+
3
6
  At its simplest, `&X` can be used as a drop-in replacement for `Symbol#to_proc`:
4
7
 
5
8
  [1,2,3].map &X.to_s
@@ -21,12 +24,18 @@ And, as everything in ruby is a method, create readable expressions without the
21
24
  ["a", "b", "c"].map &(X * 2)
22
25
  # => ["aa", "bb", "cc"]
23
26
 
27
+ [{}].each &X[1] = 2
28
+ # => [{1 => 2}]
29
+
24
30
  As an added bonus, the effect is transitive — you can chain method calls:
25
31
 
26
32
  [1, 2, 3].map &X.to_f.to_s
27
33
  # => ["1.0", "2.0", "3.0"]
28
34
 
29
- There are two things to watch out for:
35
+ Gotchas
36
+ -------
37
+
38
+ There are a few things to watch out for:
30
39
 
31
40
  Firstly, `&X` can only appear on the left:
32
41
 
@@ -52,6 +61,14 @@ Secondly, other arguments or operands will only be evaluated once, and not every
52
61
  [1, 2].map{ |x| x + (i += 1) }
53
62
  # => [2, 4]
54
63
 
64
+ Bugs
65
+ ----
66
+
67
+ If you create an assigning callable (e.g. `X[a] = b`, `X.a = b` ) without an immediate preceding `&`, then `b.class#to_proc` will return the assigning callable the first time, and only the first time, you call it. If you want to get access to an assigning callable that you've defined using `&X`, you must do: `lambda &X[a] = b` instead.
68
+
69
+
70
+ Epilogue
71
+ --------
55
72
 
56
73
  For bug-fixes or enhancements, please contact the author: Conrad Irwin <conrad.irwin@gmail.com>
57
74
 
data/lib/ampex.rb CHANGED
@@ -4,23 +4,74 @@ require 'blankslate'
4
4
  #
5
5
  # For detailed usage notes, please see README.markdown
6
6
  #
7
- class Metavariable < BlankSlate
8
- def initialize(parent=nil, &block)
9
- @block = block
10
- @parent = parent
7
+ class Metavariable < BlankSlate
8
+
9
+ # When you pass an argument with & in ruby, you're actually calling #to_proc
10
+ # on the object. So it's Symbol#to_proc that makes the &:to_s trick work,
11
+ # and Metavariable#to_proc that makes &X work.
12
+ attr_reader :to_proc
13
+
14
+ def initialize(&block)
15
+ @to_proc = block || lambda{|x| x}
11
16
  end
12
17
 
18
+ # Each time a method is called on a Metavariable, we want to create a new
19
+ # Metavariable that is like the last but does something more.
20
+ #
21
+ # The end result of calling X.one.two will be like:
22
+ #
23
+ # lambda{|x|
24
+ # (lambda{|x|
25
+ # (lambda{|x| x}).call(x).one
26
+ # }).call(x).two
27
+ # }
28
+ #
13
29
  def method_missing(name, *args, &block)
14
- Metavariable.new(self) { |x| x.send(name, *args, &block) }
30
+ mv = Metavariable.new { |x| @to_proc.call(x).send(name, *args, &block) }
31
+ Metavariable.temporarily_monkeypatch(args.last.class, mv) if name.to_s =~ /=$/
32
+ mv
15
33
  end
16
34
 
17
- def to_proc
18
- lambda do |x|
19
- if @block
20
- x = @parent.to_proc.call(x) if @parent
21
- @block.call x
22
- else
23
- x
35
+ private
36
+
37
+ # In order to support assignment via &X (expressions of the form &X['one'] = 2),
38
+ # we need to add 2.to_proc (because assignment in ruby always returns the operand)
39
+ #
40
+ # Luckily, we only need to do this for a very short time.
41
+ #
42
+ # When given an expression such as:
43
+ #
44
+ # ary.map(&X[args(a)] = :two)
45
+ #
46
+ # the order of execution is:
47
+ # args(a)
48
+ # X[_] = :two \_ need to patch here
49
+ # :two.to_proc _/ and un-patch here
50
+ # ary.map &_
51
+ #
52
+ # There is a risk if someone uses an amp-less X, and assigns something with to_proc
53
+ # (most likely a symbol), and then uses .map(&:to_i), as &:to_i will return the
54
+ # behaviour of their metavariable.
55
+ #
56
+ # There are other things that might notice us doing this, if people are listening
57
+ # on various method_added hooks, or have overridden class_eval, etc. But I'm not
58
+ # too worried.
59
+ #
60
+ def self.temporarily_monkeypatch(klass, mv)
61
+ klass.send :class_variable_set, :'@@metavariable', mv
62
+ klass.class_eval do
63
+
64
+ alias_method(:to_proc_without_metavariable, :to_proc) rescue nil
65
+ def to_proc
66
+ self.class.class_eval do
67
+
68
+ undef to_proc
69
+ alias_method(:to_proc, :to_proc_without_metavariable) rescue nil
70
+ undef to_proc_without_metavariable rescue nil
71
+
72
+ # Remove the metavariable from the class and return its proc
73
+ remove_class_variable(:'@@metavariable').to_proc
74
+ end
24
75
  end
25
76
  end
26
77
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ampex
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Conrad Irwin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-18 00:00:00 +00:00
18
+ date: 2010-11-28 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency