问题
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?
回答1:
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":
Functional programming concerns and facilitates the application and composition of functions. Further, for a language to be considered functional, its notion of function must be first-class. The functions of a language must be able to be stored, passed, and returned just like any other piece of data within that language. It’s beyond this core concept that the definitions branch toward infinity, but thankfully, it’s enough to start.
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 of f
, 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:
- Better Ruby through Functional Programming
- Thinking Functionally in Ruby (PDF)
- Thinking Functionally in Ruby (Talk)
回答2:
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.
回答3:
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 type B
, 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).
回答4:
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 when Hash#reject
does return a hash.
>> {:a => 1, :b => 2}.select { |k, v| v > 1 } #=> [[:b, 2]]
>> {:a => 1, :b => 2}.reject { |k, v| v > 1 } #=> {:a=>1}
But this was long ago regarded as a bug and fortunately solved in Ruby 1.9:
>> {:a => 1, :b => 2}.select { |k, v| v > 1 } #=> {:b=>2} # Ruby 1.9
>> {:a => 1, :b => 2}.reject { |k, v| v > 1 } #=> {:a=>1}
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 what Hash#map
should return.. an array? a hash? in Haskell for example a map
makes only sense for lists (though of complete different nature, the equivalent would be arrays in Ruby), so it would seem acceptable that Hash#map
returns an array (the alternative would be forcing the conversion to make it more clear: hash.to_a.map { |k, v| ... }
). BTW, implementing Hash#fmap
in Ruby is straighforward, I use it often and I included it in my generic extensions module:
class Hash
def fmap(&block)
Hash[self.map(&block)]
end
end
{:a => 1, :b => 2}.fmap { |k, v| [k.to_s, v*2] } #=> {"a" => 2, "b" => 4}
来源:https://stackoverflow.com/questions/5738065/is-rubys-returning-of-a-different-type-after-a-filter-unusual-from-a-functional