Does Ruby return a different type after a filter unusual for functional programming?

There are some filter functions in Ruby that produce a different type than the one you started 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 will get an array of keys and values, and 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 will get an array of characters, not a string.

Is this normal from the point of view of functional programming, or does it just indicate that Ruby does not ideally implement the functional programming paradigm?

+3
source share
4 answers

Which language "perfectly implements the functional programming paradigm"? Haskell, Erlang, Pure, OCaml, Clojure? Choose your choice, they all tend to do things differently. I am not really trying to be polemic here (I am launching a group of functional programmers where we like to discuss this type of material), but, as in the case of OOP, there are various ideas that entail functional programming.

Now that most people will not argue that Haskell has a clean field, this is far from the only way to make FP. IMHO Michael Fogus and Chris Hauser summed up the "Joy Clojure":

Functional programming facilitates the application and composition of functions. In the future, to learn the functional language, its concept of function should be first-class. The functions of the language must be saved, passed and returned just like any other piece of data within that language. Its beyond this core concept, that branch of definitions to infinity, but, fortunately, it is enough to start.

The function is no more than some kind of mapping from a domain to a codename, and two, of course, need not be the same. If you look at a function like f(x) = sqrt(x) and assume that N (natural numbers) is a domain of f , then itโ€™s clear that the codename will not be the same (unless you want the undefined function to stretch at large) .

With all that said, I do not think this behavior is particularly problematic. Type alignment (although we do not use this term in Ruby) is not the responsibility of the developer, not the language. The latter can help find these inconsistencies and also differ when they are detected (for example, compilation time and runtime).

As Mladen said, there are many things that prevent Ruby from being a purely functional language, but this is true for most languages, many of which are the functional languages โ€‹โ€‹themselves (Clojure, for example, usually promotes usability and clean pragmatism). However, it is possible to program in a very functional style in Ruby, if you really want and paid attention to some details. Here are some links to this topic:

+2
source

By definition, the functional programming paradigm avoids a state change. And by this definition and your example, I think that Ruby has not yet fully implemented this paradigm. This is obviously not scary, since you have many functions, such as a map, folds (injections), filters, etc., As well as support for lambda functions / lazy evaluation, etc.

By design, Ruby will never be a pure functional language due to its natural support for imperative / object-oriented programming. Because of this, Ruby designers may be able to balance this language with several paradigms.

+1
source

I hope we get a few more answers, as I'm also interested, but here is my opinion:

I do not understand why the type that some corelib function returns means that the language is 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 you are essentially there. Here we can simply discuss the reasons for the solutions to the above methods in order to return what they return.

There are other things that prevent Ruby from being a pure functional language (mutability, for starters).

+1
source

The existing answers have already been discussed (and well) the functional nature of Ruby (by the way, I have a post here that may be interesting). Now, answering your question: I would say that - from any point of view, not just FP, just common sense - the filter operation should always return an object with the same type of original. Some comments on your question:

1) You are wondering why Hash#find_all (= Hash#select ) returns an array. In fact, this makes no sense, especially when the Hash#reject returns the 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 has long been considered a mistake and, fortunately, was 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) The second example ( String#each_char ) is not really related to this problem. This method returns an enumerable ("lazy array", if you want) characters in a string, so select / reject / ... 'over over returns an array that is correct. Well, to be orthodox, they must return a lazy enumerator, but Ruby still has room for improvement (check out the Facets Denumerator to see a good way to do this).

3) @ Ed'ka introduced a related and interesting concept to the discussion: fmap . fmap is a generic version of the map for functors (which are just containers in which you can iterate. Examples of functors: list, tree, associative array, set, ...). In Ruby, we may wonder what Hash#map should return .. an array? hash? in Haskell, for example, map makes sense only for lists (although it has a completely different nature, the equivalent would be arrays in Ruby), so it would be acceptable for Hash#map return an array (an alternative would be a forced conversion to make it more clear: hash.to_a.map { |k, v| ... } ). BTW, Ruby's implementation of Hash#fmap is dissimilar, I use it often, and I have included it in my general extension 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} 
+1
source

All Articles