【问题标题】:Chaining & to_proc on symbol在符号上链接 & to_proc
【发布时间】:2016-04-07 09:13:03
【问题描述】:

众所周知,Rubyist & 会在符号上调用 to_proc,所以

[:a, :b, :c].map(&:to_s)

等价于

[:a, :b, :c].map { |e| e.to_s } # => ["a", "b", "c"]

假设我想在to_s 之后调用另一个方法,这两个实现都可以:

[:a, :b, :c].map { |e| e.to_s.upcase }
[:a, :b, :c].map(&:to_s).map(&:upcase)

我的问题是,有没有办法将 & Symbol#to_proc 调用链接到一个参数中?比如:

[:a, :b, :c].map(&:to_s:upcase)

谢谢!

【问题讨论】:

  • 不,没有这种方法。

标签: ruby symbols proc


【解决方案1】:

如果你只是在做:

%i[a b c].map { |e| e.to_s.upcase }

然后只需使用块并继续处理更重要的事情。如果您确实在进行一系列 Enumerable 调用,并且发现这些块在视觉上过于嘈杂:

%i[a b c].map { |e| e.to_s.upcase }.some_chain_of_enumerable_calls...

然后你可以把你的逻辑扔进一个 lambda 来帮助清理外观:

to_s_upcase = lambda { |e| e.to_s.upcase }
%i[a b c].map(&to_s_upcase).some_chain_of_enumerable_calls...

或者把它扔到一个方法中然后说:

%i[a b c].map(&method(:to_s_upcase)).some_chain_of_enumerable_calls...

无论哪种方式,您都在为您的一点点逻辑命名(这几乎是&:symbol 为您所做的所有事情),以使代码更具可读性和更易于理解。在to_s.upcase 的具体情况下,这有点毫无意义,但是当块变大时,这些方法非常有用。

【讨论】:

    【解决方案2】:

    您需要提前定义一些方法,但这将具有通用性。你可以这样做:

    class Symbol
      def * other
        ->x{x.send(self).send(other)}
      end
    end
    
    [:a, :b, :c].map(&:to_s * :upcase)
    [:a, :b, :c].map(&:to_s * :capitalize)
    ...
    

    我选择*作为函数组合的方法。

    如果你认为你可能会使用第三个符号,你可以定义如下:

    class Proc
      def * other
        ->x{call(x).send(other)}
      end
    end
    

    【讨论】:

    • 我喜欢这个想法,但感觉太聪明了,太有创意了。如果* 运算符在您的代码库中不是很为人所知,那么从现在起一年后,您将查看&:to_s * :upcase 行并且不知道它做了什么,也不知道它做了什么。
    • @Gaston 我同意这不是很容易解释的想法,但是这部分 “也不知道如何找出它的作用” 实际上相当简单。由于这是用纯 ruby​​ 编写的(不是 C 扩展):to_s.method(:*).source_location,它会告诉您此方法的源代码的位置,因此您可以确定它的作用。
    • @engineersmnky 哇!我不知道.source_location!不错。
    【解决方案3】:

    所以只是为了好玩(并且为了证明只要付出一点努力,在 ruby​​ 中几乎任何事情都是可能的)我们可以在 Symbol(我们称之为 Symbol#chain)上定义一个方法来提供这个功能还有一点

    class Symbol
      def proc_chain(*args)
        args.inject(self.to_proc) do |memo,meth|
          meth, *passable_args = [meth].flatten
          passable_block = passable_args.pop if passable_args.last.is_a?(Proc)
          Proc.new do |obj|
            memo.call(obj).__send__(meth,*passable_args,&passable_block)
          end
        end
      end
      alias_method :chain, :proc_chain
    end
    

    然后可以这样调用

    [:a, :b, :c].map(&:to_s.chain(:upcase))
    #=> ["A","B","C"]
    # Or with Arguments & blocks
    [1,2,3,4,5].map(&:itself.chain([:to_s,2],:chars,[:map,->(e){ "#{e}!!!!"}]))
    #=>  => [["1!!!!"], ["1!!!!", "0!!!!"], ["1!!!!", "1!!!!"],
    #        ["1!!!!","0!!!!", "0!!!!"], ["1!!!!", "0!!!!", "1!!!!"]]
    

    甚至可以单独使用

    p = :to_s.chain([:split,'.'])
    p.call(123.45)
    #=> ["123","45"]
    # Or even 
    [123.45,76.75].map(&p)
    #=> => [["123", "45"], ["76", "75"]]
    

    【讨论】:

    • @JordanRunning 虽然高尔夫球比我想的要高,但我觉得这不是很酷
    • 哦,我不认为它实际上有用,但作为一个概念验证,它是惊人的。
    【解决方案4】:

    当我们在玩语法时,用 to_proc 方法给猴子打补丁怎么样?

    class Array
      def to_proc
        return :itself.to_proc if empty?
        ->(obj) { drop(1).to_proc.call(first.to_proc.call(obj)) }
      end
    end
    
    ["foo", "bar", "baz"].map(&[:capitalize, :swapcase, :chars, ->a{ a.join("-") }])
    # => ["f-O-O", "b-A-R", "b-A-Z"]
    

    在 repl.it 上查看:https://repl.it/JS4B/1

    【讨论】:

      【解决方案5】:

      没有办法使用符号链接到 proc。

      但是,您可以将一个方法修补到您正在映射的类上,这两者都可以,然后调用它。

      class Symbol
        def to_upcase_str
          self.to_s.upcase
        end
      end
      
      [:a, :b, :c].map(&:to_upcase_str)
      

      【讨论】:

      • 您忘记为数组调用map 方法
      • 这比使用块有什么好处?为什么猴子补丁Symbol 只针对一个非常具体的场景?
      • @Wand,Austio 没有提出这样的要求。如果答案只包含更好的做某事的方法,我们都会变得更穷。同样,问答也不会那么有趣。
      • @WandMaker,cary 是正确的,只是他如何完成他想要的事情的例子。 Ruby 允许不同的方法来解决问题,这个答案可以帮助其他人在谷歌上搜索那些不太了解 ruby​​ 领域的答案。我同意在大多数情况下只使用一个块会更好。
      【解决方案6】:

      我很惊讶没有人提到 Proc#<<Proc#>>

      [:a, :b, :c].map(&(:to_s.to_proc << :upcase.to_proc))
      # => ["A", "B", "C"]
      [:a, :b, :c].map(&(:upcase.to_proc >> :to_s.to_proc))
      # => ["A", "B", "C"]
      

      参考:https://ruby-doc.org/core-2.7.2/Proc.html#method-i-3C-3C

      【讨论】:

      • 没有提到它,因为在 2017 年的黑暗时代,我们运行 2.5 作为 Ruby 的前沿。 ruby-doc.org/core-2.5.0/Proc.html 但现在是的,虽然我仍然更喜欢类似长生不老药的运算符 (|&gt;) 用于管道,但现在可以使用这种语法。
      猜你喜欢
      • 1970-01-01
      • 2010-11-29
      • 2014-11-22
      • 1970-01-01
      • 2017-01-05
      • 1970-01-01
      • 2012-02-01
      • 2012-05-14
      • 2021-10-31
      相关资源
      最近更新 更多