从函数式编程的角度来看,Ruby 在过滤器之后返回不同的类型是否不寻常?
在 Ruby 中,有一些过滤函数会生成与您开始时不同的类型。
例如,如果这样做,
{a: 2, b: 0}.find_all{|key, value| value.zero?}
# Use Hash[new_array] to turn it into a hash
您最终会得到一个键和值的数组,而不是另一个散列。
如果这样做,
str = "happydays"
all_indexes = [1, 2, 7, 8]
str.each_char.reject.with_index{|char, index| all_indexes.include?(index)}
# Use .join to turn it into a string
您最终会得到一个字符数组,而不是一个字符串。
从函数式编程的角度来看,这是正常的,还是这仅仅表明 Ruby 没有完美地实现函数式编程范例?
In Ruby, there are some filter functions that produce a different type than what you started off with.
For example, if you do
{a: 2, b: 0}.find_all{|key, value| value.zero?}
# Use Hash[new_array] to turn it into a hash
you end up with an array of keys and values, not another hash.
And if you do
str = "happydays"
all_indexes = [1, 2, 7, 8]
str.each_char.reject.with_index{|char, index| all_indexes.include?(index)}
# Use .join to turn it into a string
you end up with an array of characters, rather than a string.
Is this normal from a functional programming perspective, or does this merely indicate that Ruby doesn't perfectly implement the functional programming paradigm?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
哪种语言“完美实现了函数式编程范式”? Haskell、Erlang、Pure、OCaml、Clojure?选择你的选择,他们做事的方式往往截然不同。我真的不想在这里争论(我运行一个函数式编程用户组,我们喜欢讨论此类内容),但与 OOP 一样,函数式编程需要有不同的想法。
现在,虽然大多数人不会认为 Haskell 在纯度方面处于领先地位,但这绝不是进行 FP 的唯一方法。恕我直言,Michael Fogus 和 Chris Houser 在《Clojure 的乐趣》中总结得很好:
函数实际上只不过是从域到共域的某种映射,并且两者肯定不必相同。如果您查看像
f(x) = sqrt(x)
这样的函数,并假设 N(自然数)是f
的域,则很明显codomain 不会相同(除非您想要一个在很大范围内未定义的函数)。话虽如此,我认为这种行为并不是特别有问题。对齐类型(尽管我们在 Ruby 中不常用这个术语)是开发人员的责任,而不是语言的责任。后者可以帮助找到这些不匹配,并且在找到它们的时间上也有所不同(例如编译时与运行时)。
正如 Mladen 所说,有很多因素阻止 Ruby 成为纯粹的函数式语言,但对于大多数语言来说都是如此,其中相当多的语言本身就是函数式语言(例如,Clojure 通常更倾向于可用性和实用主义而不是纯粹性)。然而,如果人们真的想要并注意一些细节,那么在 Ruby 中以一种非常函数式的风格进行编程是很有可能的。以下是有关该主题的一些链接:
Which language does "perfectly implement the functional programming paradigm"? Haskell, Erlang, Pure, OCaml, Clojure? Pick your choice, they all tend to do things quite differently. I'm really not trying to be polemic here (I run a functional programming user group where we love discussing this type of stuff), but as with OOP, there are different ideas as what functional programming entails.
Now while most people wouldn't argue that Haskell leads the field in purity, it's by no means the only way to do FP. IMHO Michael Fogus and Chris Houser summed it up quite well in "The Joy of Clojure":
A function isn't really more than some sort of mapping from the domain to the codomain, and the two most certainly don't have to be the same. If you look at a function like
f(x) = sqrt(x)
and assume N (the natural numbers) to be the domain off
, it's quite obvious that the codomain will not be the same (unless you want a function that's undefined over large stretches).With all that said, I don't think that this behavior is specifically problematic. Aligning the types (even though we don't commonly use this term in Ruby) is the responsibility of the developer, not the language. The latter can assist in finding those mismatches and also differ in when they find them (e.g. compile time vs run time).
As Mladen said, there are lots of things that prevent Ruby from being a purely functional language, but that's true for most languages, quite a few of them being functional languages themselves (Clojure e.g. commonly favors usability and pragmatism over purity). It is however quite possible to program in a very functional style in Ruby, if one really wants to and pays attention to some details. Here are some links on the topic:
根据定义,函数式编程范式避免了状态的改变。根据这个定义和你给出的例子,我认为 Ruby 还没有完美地实现这个范例。显然这并不可怕,因为你有很多函数,如映射、折叠(注入)、过滤器等,并且支持 lambda 函数/惰性求值等。
从设计上来说,Ruby 永远不会是一种纯函数式语言,因为它的天然特性支持命令式/面向对象编程。正因为如此,Ruby 的设计者能做的只有这么多来平衡这种多范式语言。
By definition the functional programming paradigm avoids change in state. And by this definition and your given example, I think Ruby hasn't perfectly implemented this paradigm yet. It obviously isn't terrible, as you have plenty of functions such as map, folds(inject), filters, etc and have support for lambda functions/lazy evaluation etc.
By design Ruby will never be a pure functional language because of its natural support for imperative/object oriented programming. Because of this, there is only so much the designers of Ruby can do to balance this multi-paradigm language.
希望我们能得到更多答案,因为我也很感兴趣,但这是我的观点:
我不明白为什么某些 corelib 函数返回的 type 会暗示一种语言功能较少。采用任何纯函数式语言,您都可以实现一个接受
A
类型的函数并返回B
类型的函数,这就是您本质上所拥有的。我们在这里可以只讨论上述方法返回其返回内容的决策背后的原因。还有其他一些因素阻碍 Ruby 成为纯函数式语言(首先是可变性)。
Hopefully we'll get some more answers as I am quite interested as well, but here's my opinion:
I don't see why the type which some corelib function returns would imply a language being less functional. Take any pure functional language and you can implement a function that takes something of type
A
and returns something of typeB
, and that's what you essentially have up there. We can here just discuss reasons behind the decisions for above methods to return what they return.There are other things which prevent Ruby from being a pure functional language (mutability, for start).
现有的答案已经讨论了(并且很好)Ruby 的功能性质(顺便说一句,我有一篇文章
1)您想知道为什么
Hash#find_all
(=Hash#select
) 返回一个数组。事实上,当Hash#reject
返回哈希时,这毫无意义。但这很久以前就被认为是一个错误,幸运的是在 Ruby 1.9 中得到了解决:
2)你的第二个例子(
String#each_char
)实际上与这个问题无关。此方法返回字符串中字符的可枚举(如果您愿意,可以是“惰性数组”),因此对其进行 select/reject/... 会返回一个数组,这是正确的。好吧,为了正统,他们应该返回一个惰性枚举器,但是 Ruby 这里仍然有改进的空间(检查 Facets' 分数来查看应该完成的好方法)。3) @Ed'ka 在讨论中引入了一个相关且有趣的概念:
fmap
。fmap
是函子映射的通用版本(函子只是可以迭代的容器。函子的示例:列表、树、关联数组、集合……)。在 Ruby 中,我们可能想知道Hash#map
应该返回什么……数组?哈希?例如,在 Haskell 中,map
仅对列表有意义(尽管性质完全不同,但 Ruby 中的数组是等效的),因此Hash#map
似乎可以接受返回一个数组(另一种方法是强制转换以使其更清晰:hash.to_a.map { |k, v| ... }
)。顺便说一句,在 Ruby 中实现Hash#fmap
非常简单,我经常使用它,并将其包含在我的通用扩展模块中:The existing answers already discussed (and well) the functional nature of Ruby (by the way, I have a writing here that may be interesting). Now, answering your question: I'd say that -from any perspective, not only FP, just common sense- a filter operation should always return an object with the same type of the original. Some comments about your question:
1) You wonder why
Hash#find_all
(=Hash#select
) returns an array. Indeed, that makes no sense, the more whenHash#reject
does return a hash.But this was long ago regarded as a bug and fortunately solved in Ruby 1.9:
2) Your second example (
String#each_char
) it's not actually related with this problem. This method returns an enumerable (a "lazy array", if you wish) of the chars in the string, so select/reject/...'ing over it returns an array, that's correct. Well, to be orthodox they should return also a lazy enumerator, but Ruby has still room for improvement here (check Facets' Denumerators to see the nice way it should be done).3) @Ed'ka introduced a related and interesting concept to the discussion:
fmap
.fmap
is a generalized version of map for functors (which are just containers where you can iteratate over. Examples of functors: a list, a tree, an associative array, a set, ...). In Ruby we may wonder whatHash#map
should return.. an array? a hash? in Haskell for example amap
makes only sense for lists (though of complete different nature, the equivalent would be arrays in Ruby), so it would seem acceptable thatHash#map
returns an array (the alternative would be forcing the conversion to make it more clear:hash.to_a.map { |k, v| ... }
). BTW, implementingHash#fmap
in Ruby is straighforward, I use it often and I included it in my generic extensions module: