Ruby 中类似 Elixir 的管道来处理集合
Posted
技术标签:
【中文标题】Ruby 中类似 Elixir 的管道来处理集合【英文标题】:Elixir-like pipes in Ruby to process collections 【发布时间】:2022-01-02 18:10:26 【问题描述】:在 Elixir 中有一个很棒的管道操作员是这样工作的:
"hello, world!"
|> String.split(" ")
|> Enum.map(&String.capitalize/1)
|> Enum.join
在 Ruby 中我们可以使用类似的语法:
"hello, world!"
.split(" ")
.map(&:capitalize)
.join
只有在为对象本身定义了所有这些方法时才有效。如果需要调用一些本地方法,我们应该使用类似:
.map |el| URI.parse(el)
但是如果我们想做一些集合处理(不是单个元素),例如 GZIP 压缩:
chars = text
.downcase
.chars
compressed = GZipped.new(chars).bytes
但是链条断了!
我找到了一些链接,但看起来不太好:
pipe_envy - 丑陋!没有收藏 chainable_methods - 没有收藏 How to use chainable_methods piperator - 好多了!但看起来很重在我看来,有这样的东西会很棒:
text
.split
.pipe(URI.method(:parse))
.map(&:to_s)
.join
.pipe(GZIPped)
.pipe(Base64.method(:encode))
在 Ruby 中构建此类管道的最佳方法是什么?
更新 1
这是一个例子
class Dedup
def initialize(obj)
@obj = obj
end
def each
Enumerator.new do |y|
prev = nil
@obj.each do |el|
if el != prev
y << el
prev = el
end
end
end
end
end
expect(
"1 1 1 2 2 3"
.split
.then |obj| Dedup.new(obj).each
.to_a
).to eq [1, 2, 3]
这种链接看起来丑陋且难以阅读。
比较:
expect(
"1 1 1 2 2 3"
.split
.pipe(Dedup)
.to_a
).to eq [1, 2, 3]
【问题讨论】:
根据我的经验,试图让一种语言模仿另一种语言很少奏效。我宁愿以惯用代码为目标,使用 Ruby 提供的语言工具。 @Stefan 你能提供一些idiomatic
的 Ruby 风格的代码吗?
在elixir 中也不鼓励为了管道而进行管道。声明一个局部变量 chars
并在其上调用 GZipped.new(chars)
。除非你在愚弄队友之后,否则长管道没有什么好处。
@SergeiO.Udalov 示例代码似乎很做作。你有一个实际的问题想要解决吗?
是的。真实案例是将elixir代码移植到ruby。是的,我们可以用 ruby 方式重写它,但最好保持尽可能接近。
【参考方案1】:
已经有这样的方法,至少从 Ruby 2.5 开始 - yield_self
,在 Ruby 2.6 中别名为 then
。您可以将&
运算符与响应to_proc
的任何对象一起使用来传递它而不是块。
text
.split
.map(&URI.method(:parse)) # URI#parse expects a string, not an array
.map(&:to_s)
.join
.then(&GZIPped) # not sure what GZIPped is - I'll assume it has .to_proc method
.then(&Base64.method(:encode))
(我可能应该提到上面的代码实际上不会工作,老实说,我不知道它会做什么 - 为什么要拆分字符串,将它们转换为 url,然后再返回到字符串?唯一的事情这是为了提高 id 其中一个 substrngs 不是有效字符串?但是然后您尝试将结果字符串读取为 gzip 压缩文件...我假设我误解了您的代码中的某些内容)
更高级的东西——我非常喜欢 elixir 中的一件事,就是可以将方法与剩余的参数链接在一起。这也可以在 ruby 中进行模拟,但需要一些工作并仔细考虑是否值得:
module MyMath
module_function
UNDEFINED = Object.new
def add(a, b = UNDEFINED)
if b == UNDEFINED
return ->(num) add(a, num)
end
a + b
end
end
MyMath.add(2,5) #=> 7
[1,2,5,9].map(&MyMath.add(5)] #=> [6,7,10,14]
【讨论】:
总有.then |obj| ...
允许任意转换。
@Stefan @BroiSatse 你能看看我提供的“更新 1”吗?无法理解如何使用 .then
使其可读
@SergeiO.Udalov - 老实说,这在 ruby 中将是一件非常难以理解的事情 - 您正在尝试混合功能和面向对象的编程。重复数据删除不是功能。 Buttttt,您可以定义返回->(obj) self.new(obj).each
的self.to_proc
方法,您可以使用then(&Dedup)
。但是,这很奇怪
真实案例是将elixir代码移植到ruby。是的,我们可以用 ruby 方式重写它,但最好让它尽可能接近。 self.to_proc
为我工作!非常感谢,伙计!
次要注释MyMath#add
是一个实例方法,因此无法作为MyMath.add()
访问,您需要将方法定义更改为self.add
或添加module_function :add
。以上是关于Ruby 中类似 Elixir 的管道来处理集合的主要内容,如果未能解决你的问题,请参考以下文章