A908bf6680c4fa7279dd85d5012f7ed8

Reginald Braithwaite has a very interesting article on his blog at http://weblog.raganwald.com/2007/10/stringtoproc.html where he presents a port for Ruby of String Lambdas from Oliver Steele’s Functional Javascript. It's exceedingly cool :-)
Go read the article to see what it does first.
I thought I could post it here when I saw Giles Bowkett used pastie to submit a suggestion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# String#to_proc
#
# See http://weblog.raganwald.com/2007/10/stringtoproc.html ( Subscribe in a reader)
#
# Ported from the String Lambdas in Oliver Steele's Functional Javascript
# http://osteele.com/sources/javascript/functional/
#
# This work is licensed under the MIT License:
#
# (c) 2007 Reginald Braithwaite
# Portions Copyright (c) 2006 Oliver Steele
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

class String
  unless ''.respond_to?(:to_proc)
    def to_proc &block
      params = []
      expr = self
      sections = expr.split(/\s*->\s*/m)
      if sections.length > 1 then
          eval sections.reverse!.inject { |e, p| "(Proc.new { |#{p.split(/\s/).join(', ')}| #{e} })" }, block && block.binding
      elsif expr.match(/\b_\b/)
          eval "Proc.new { |_| #{expr} }", block && block.binding
      else
          leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m)
          rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m)
          if leftSection || rightSection then
              if (leftSection) then
                  params.push('$left')
                  expr = '$left' + expr
              end
              if (rightSection) then
                  params.push('$right')
                  expr = expr + '$right'
              end
          else
              self.gsub(
                  /(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, ''
              ).scan(
                /([a-z_$][a-z_$\d]*)/i
              ) do |v|  
                params.push(v) unless params.include?(v)
              end
          end
          eval "Proc.new { |#{params.join(', ')}| #{expr} }", block && block.binding
      end
    end
  end
end

Refactorings

No refactoring yet !

7c45f63f61e478233f0c2ad3006b178c

michiel

October 28, 2007, October 28, 2007 18:35, permalink

1 rating. Login to rate!

Ok, I've removed the nested ifs and I've introduced guards to quicky exit the method if possible. But I've made no attempt to parse the (uncommented) regular expressions, which do most of the actual work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class String
  unless ''.respond_to?(:to_proc)
    def to_proc(&block)
      evaller = lambda {|ex| eval(ex, block && block.binding) }
      prc_templ = lambda {|prm,ex| 'Proc.new { |' + prm.join(', ') + "| #{ex} }" }
      expr = self
      sections = expr.split(/\s*->\s*/m).reverse
      return eval(sections.inject { |e, p| evaller["(" + prc_templ[p.split(/\s/), e] + ")"] }) if sections.length > 1
      return evaller[prc_templ[['_'],expr]] if expr.match(/\b_\b/)
      params = []
      if expr.match(/^\s*(?:[+*\/%&|\^\.=<>\[]|!=)/m)
        params.push('$left')
        expr = '$left' + expr
      end
      if expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m)
        params.push('$right')
        expr += '$right'
      end
      if params.empty?
        x = self.gsub(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|self|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/, '')
        x.scan(/([a-z_$][a-z_$\d]*)/i) {|v|  params.push(v) unless params.include?(v) }
      end
      evaller[prc_templ[params,expr]]
    end
  end
end
Bfec5f7d1a4aaafc5a2451be8c42d26a

macournoyer

October 30, 2007, October 30, 2007 14:24, permalink

1 rating. Login to rate!

I refactored it for the simplest and most common use cases, just chaining method calls, what Symbol#to_proc doesn't allow you to do

1
2
3
4
5
6
7
8
class String
  def to_proc
    eval "proc { |s| s#{self} }"
  end
end

(1..10).collect(&'*2') # => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
%w(hi there my friend).collect(&".reverse.capitalize") # => ["Ih", "Ereht", "Ym", "Dneirf"]
A908bf6680c4fa7279dd85d5012f7ed8

webmat

October 30, 2007, October 30, 2007 14:50, permalink

No rating. Login to rate!

Nice!
I should post the rspec tests too, to update them (they're on the original article). I'll do that _after_ my presentation at MontrealonRails next tuesday, though. I'm busy enough preparing for that ;-)

Your refactoring





Format Copy from initial code

or Cancel