ampex 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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