简体   繁体   English

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

[英]Implement to_s(2) with String#to_proc in Ruby

I'm learning about the unary operator, & . 我正在学习一元算子, &

There are some great questions about using & in the parameters of a method invocation. 关于在方法调用的参数中使用&有一些很好的问题。 Usually the format goes something like some_obj.some_method(&:symbol) : 通常格式类似于some_obj.some_method(&:symbol)

It seems like the main idea is ruby calls the to_proc method on :symbol when the unary operator is placed in front of the symbol. 似乎主要思想是ruby调用to_proc方法:symbol当一元运算符放在符号前面时符号。 Because Symbol#to_proc exists "everything works". 因为Symbol#to_proc存在“一切正常”。

I'm still confused about how everything just works. 我仍然对一切如何运作感到困惑。

What if I want to implement a "to_proc sort of functionality with a string". 如果我想用字符串实现“to_proc类功能”,该怎么办? I'm putting it in quotes because I'm not really sure how to even talk about what I'm trying to do. 我把它放在引号中因为我不确定如何谈论我正在尝试做什么。

But the goal is to write a String#to_proc method such that the following works: 但目标是编写一个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"]

This is how I did it: 我就是这样做的:

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"]

The main part I'm confused about is how I get the parameters into the String#to_proc method. 我很困惑的主要部分是如何将参数输入String#to_proc方法。 It seems like: 这好像是:

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

Should be: 应该:

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

Or something like that. 或类似的东西。 How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns? [2, 4, 6, 8]值如何进入String#to_proc返回的proc?

Just write this 写这个

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

Though I guess that is not what you're looking for … 虽然我猜这不是你想要的......

Here is how Symbol#to_proc is implemented. 以下是Symbol#to_proc的实现方式。

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

If you want you can define to_proc on an Array as follows 如果需要,可以按如下方式在Array上定义to_proc

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

And then use 然后使用

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

Another alternative is using curry . 另一种选择是使用curry

Though that does not work with bound methods, so you'll have to define a to_s lambda function first. 虽然这不适用于绑定方法,但您必须首先定义to_s lambda函数。

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

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

Though all of that seems more like academic exercises. 虽然所有这些看起来更像是学术练习。

When you run some_method(&some_obj) , Ruby first call the some_obj.to_proc to get a proc, then it "converts" that proc to a block and passes that block to some_method . 当你运行some_method(&some_obj) ,Ruby首先调用some_obj.to_proc来获取一个proc,然后它将proc“转换”为一个块并将该块传递给some_method So how the arguments go into the proc depends on how some_method passes arguments to the block. 那么参数如何进入proc取决于some_method如何将参数传递给块。

For example, as you defined String#to_proc , which returns a proc{|arg| ...} 例如,当您定义String#to_proc ,它返回一个proc{|arg| ...} proc{|arg| ...} (a proc with one argument), and calls [...].map(&'to_s 2') , Ruby interprets it as proc{|arg| ...} (带有一个参数的proc),并调用[...].map(&'to_s 2') ,Ruby将其解释为

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

which is 是的

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

and finally 最后

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

The problem with your approach is that there's no way to deduce the type of the argument when it's always passed as a string. 你的方法的问题是,当它总是作为字符串传递时,没有办法推断出参数的类型。

By the way, to address your question: 顺便说一下,要解决你的问题:

How do the [2, 4, 6, 8] values get into the proc that String#to_proc returns? [2,4,6,8]值如何进入String#to_proc返回的proc?

They are some_arg here, which is not a variable you have to define but instead is a parameter that is automatically passed when the proc is called. 它们在这里是some_arg ,它不是你必须定义的变量,而是一个在调用proc时自动传递的参数。

Here's a rewriting of the String patch and some usage examples: 这里是String补丁的重写和一些用法示例:

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

This works for the following example: 这适用于以下示例:

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

but fails for this (your example): 但是失败了(你的例子):

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

The problem is to_s('2') is being called, when the 2 should be an integer, not a string. 当2应该是整数而不是字符串时,问题是调用to_s('2') I can't think of any way to get around this except for maybe some serialization (although one of the other answers shows how eval can work). 除了可能的一些序列化之外,我想不出任何解决方法(尽管其他一个答案显示了eval如何工作)。

Now that the limitations of this approach are clear, it's worth comparing it to the more commonly used patch on Symbol to enable argument passing to proc shorthands (this taken from can-you-supply-arguments-to-the-mapmethod-syntax-in-ruby ) 既然这种方法的局限性是明确的,那么值得将它与Symbol上更常用的补丁相比较,以便将参数传递给proc shorthands(这取自can-you-supply-arguments-to-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] 

This way you can pass any type of arguments to the proc, not just strings. 这样,您可以将任何类型的参数传递给proc,而不仅仅是字符串。

Once you've defined it, you can easily swap out String for Symbol: 一旦定义了它,就可以轻松地换掉String for Symbol:

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

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

Refactored code 重构代码

You're free to choose the name for the proc block variable. 您可以自由选择proc块变量的名称。 So it could be yet_another_arg , some_arg or something_else . 所以它可能是yet_another_argsome_argsomething_else In this case, the object you're passing to to_proc is actually the object you want to receive the proc call, so you could call it receiver . 在这种情况下,您传递给to_proc的对象实际上是您想要接收 proc调用的对象,因此您可以将其称为receiver The method and param are in the String, so you get them with String#split from self . 方法和参数在String中,因此您可以使用String#split from self

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"]

Note that this method has been tailored to accept one method name and one integer argument. 请注意,此方法已定制为接受一个方法名称和一个整数参数。 It doesn't work in the general case. 它在一般情况下不起作用。

Another possibility 另一种可能性

Warning 警告

eval is evil 评估邪恶的

You've been warned 你被警告过了

This works with the exact syntax you wanted, and it also works for a wider range of methods and parameters : 这适用于您想要的确切语法,它也适用于更广泛的方法和参数:

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]]

It also brings security problems, though. 但它也带来了安全问题。 With great power comes great responsibility! 拥有权利的同时也被赋予了重大的责任!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM