在Ruby中使用String#to_proc实现to_s(2)

时间:2016-12-28 23:43:38

标签: ruby block

我正在学习一元算子&

在方法调用的参数中使用&有一些很好的问题。通常格式类似于some_obj.some_method(&:symbol)

当一元运算符放在符号前面时,似乎主要思想是ruby调用to_proc上的:symbol方法。因为Symbol#to_proc存在"一切正常"。

我仍然对一切如何运作感到困惑。

如果我想用字符串"实现" to_proc类功能,该怎么办?我把它放在引号中是因为我不确定如何谈论我想要做的事情。

但目标是编写一个String#to_proc方法,以便以下方法起作用:

class String
  def to_proc # some args?
    Proc.new do
      # some code?
    end
  end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]

我就这样做了:

class String
  def to_proc
    Proc.new do |some_arg|
      parts = self.split(/ /)
      some_proc = parts.first.to_sym.to_proc
      another_arg = parts.last.to_i
      some_proc.call(some_arg, another_arg)
    end
  end
end
p result = [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]

我很困惑的主要部分是如何将参数输入String#to_proc方法。好像是:

def to_proc
  Proc.new do |some_arg| ...
end

应该是:

def to_proc some_arg
  Proc.new do |yet_another_arg| ...
end

或类似的东西。 [2, 4, 6, 8]值如何进入String#to_proc返回的过程?

4 个答案:

答案 0 :(得分:4)

请写下这个

[2, 4, 6, 8].map { |each| each.to_s(2) }

虽然我猜这不是你要找的......

以下是Symbol#to_proc的实施方式。

class Symbol
  def to_proc
    proc { |each| each.send(self) }
  end
end

如果需要,可以按如下方式在数组上定义to_proc

class Array
  def to_proc
    symbol, *args = self
    proc { |each| each.send(symbol, *args) }
  end
end

然后使用

[2, 4, 6, 8].map(&[:to_s, 2])

另一种选择是使用curry

虽然这不适用于绑定方法,因此您必须首先定义to_s lambda函数。

to_s = lambda { |n, each| each.to_s(n) }

[2, 4, 6, 8].map(&to_s.curry[2])

虽然所有这些看起来更像是学术练习。

答案 1 :(得分:3)

当你运行some_method(&some_obj)时,Ruby首先调用some_obj.to_proc来获取一个proc,然后它"转换" proc到块并将该块传递给some_method。那么参数如何进入proc取决于some_method如何将参数传递给块。

例如,当您定义String#to_proc,它返回proc{|arg| ...}(带有一个参数的proc),并调用[...].map(&'to_s 2')时,Ruby会将其解释为

[...].map(&('to_s 2'.to_proc))

[...].map(&proc{|arg| ... })

最后

[...].map {|arg| ... }

答案 2 :(得分:2)

你的方法的问题是,当它总是以字符串形式传递时,无法推断出参数的类型。

顺便说一句,要解决你的问题:

  

[2,4,6,8]值如何进入String#to_proc返回的proc?

这里它们是some_arg,它不是你必须定义的变量,而是一个在调用proc时自动传递的参数。

这里是String补丁的重写和一些用法示例:

class String
  def to_proc
    fn, *args = split ' '
    ->(obj) { obj.send(fn.to_sym, *args) }
  end
end

这适用于以下示例:

p result = [[1,2,3]].map(&"join -")
# => ['1-2-3']

但是失败了(你的例子):

p result = [2, 4, 6, 8].map(&'to_s 2')
# => TypeError

当2应该是整数而不是字符串时,问题是to_s('2')被调用。除了可能的一些序列化之外,我想不出任何解决方法(尽管其中一个答案显示了eval如何工作)。

既然这种方法的局限性很明显,那么值得将它与Symbol上更常用的补丁进行比较,以使参数传递给proc短号(这取自can-you-supply-arguments-to-the-mapmethod-syntax-in-ruby

class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

这样你可以将任何类型的参数传递给proc,而不仅仅是字符串。

一旦定义了它,就可以轻松地换掉String for Symbol:

class String
  def call(*args, &blk)
    to_sym.call(*args, &blk)
  end
end

puts [1,2,3].map(&'+'.(1))

答案 3 :(得分:1)

重构代码

您可以自由选择proc块变量的名称。因此可以是yet_another_argsome_argsomething_else。在这种情况下,您传递给to_proc的对象实际上是您想要receive proc调用的对象,因此您可以将其称为receiver。方法和参数位于字符串中,因此您可以使用self中的String#split获取它们。

class String
  def to_proc
    proc do |receiver|
      method_name, param = self.split
      receiver.method(method_name.to_sym).call(param.to_i)
    end
  end
end

p result = [2, 4, 6, 8].map(&'to_s 2')
# => ["10", "100", "110", "1000"]

请注意,此方法已经过定制,可以接受一个方法名称和一个整数参数。它在一般情况下不起作用。

另一种可能性

警告

evalevil

你被警告了

这适用于您想要的确切语法,它也适用于更广泛的方法和参数:

class String
  def to_proc
    proc { |x| eval "#{x.inspect}.#{self}" }
  end
end

p [2, 4, 6, 8].map(&'to_s 2')
#=> ["10", "100", "110", "1000"]
p ["10", "100", "110", "1000"].map(&'to_i 2')
#=> [2, 4, 6, 8]
p [1, 2, 3, 4].map(&'odd?')
#=> [true, false, true, false]
p %w(a b c).map(&'*3')
#=> ["aaa", "bbb", "ccc"]
p [[1,2,3],[1,2],[1]].map(&'map(&"*2")')
#=> [[2, 4, 6], [2, 4], [2]]

但它也带来了安全问题。强大的力量带来了巨大的责任!