I'm looking for examples of functional code in ruby. Maybe you know some gems, where I can find such a code?
问题:
回答1:
I've been gathering examples of functional programming with Ruby:
- Wiki page
- Presentation: Functional programming with Ruby.
回答2:
Have you looked at Enumerable?
(1..100).select { |i| i % 5 == 0 }.map { |i| i * 5 }.take(3) #=> [25, 50, 75]
回答3:
A couple of months ago, I wondered whether it was possible to make a mixin that behaves exactly like Enumerable
, but is based on folding instead of enumerating. Or more precisely, I already knew that it was possible to do this, since fold is a generic iteration operation that is equal in expressive power to foreach, but I wanted to know what it would look like and how hard it would be.
I got bored after I had implemented most methods up to the letter g, so it's incomplete. Also, the version I show here is simplified, since making it behave exactly like Enumerable
is a PITA in Ruby. (For example, there are overloaded methods in there, but Ruby doesn't support overloading. This isn't a problem for most Ruby implementations, because they implement Enumerable
in Java or C# or other languages that do support overloading, but it is pretty painful when doing it in pure Ruby, so I decided to leave it out.)
Enumerable
is full of higher-order procedures, and of course fold (or reduce
/ inject
as it is called in Ruby) is itself a higher-order procedure, so this code is full of them.
module Enumerable
def all?
reduce(true) {|res, el| res && yield(el) }
end
def any?
reduce(false) {|res, el| res || yield(el) }
end
def collect
reduce([]) {|res, el| res + yield(el) }
end
alias_method :map, :collect
def count
reduce(0) {|res, el| res + 1 if yield el }
end
def detect
reduce(nil) {|res, el| if yield el then el end unless res }
end
alias_method :find, :detect
def drop(n=1)
reduce([]) {|res, el| res.tap {|res| res + el unless n -= 1 >= 0 }}
end
def each
reduce(nil) {|_, el| yield el }
end
def each_with_index
reduce(-1) {|i, el| (i+1).tap {|i| yield el, i }}
end
def find_all
reduce([]) {|res, el| res.tap {|res| res + el if yield el }}
end
alias_method :select, :find_all
def grep(pattern)
reduce([]) {|res, el| res.tap {|res| res + yield(el) if pattern === el }}
end
def group_by
reduce(Hash.new {|hsh, key| hsh[key] = [] }) {|res, el| res.tap {|res|
res[yield el] << el
}}
end
def include?(obj)
reduce(false) {|res, el| break true if res || el == obj }
end
def reject
reduce([]) {|res, el| res.tap {|res| res + el unless yield el }}
end
end
You will notice that I use side-effects in some places. I could have written it without side-effects instead, but it would have been much more complicated. And the problem is that there is no way for me to ensure that the reduce
method, which is provided by the client of the mixin, or any of the methods from the core library, don't have side-effects.
Another nice example is the Prelude library for Ruby, which is an implementation of some parts of the Haskell Prelude (Haskell's equivalent to Ruby's core library) in Ruby.
回答4:
You might be interested in the talk "Better Ruby through Functional Programming" by Dean Wampler, it shows how to use a FP style with Ruby:
http://vimeo.com/6701329
There's also "Thinking functionally in Ruby" by Tom Stuart:
http://skillsmatter.com/podcast/ajax-ria/enumerators
回答5:
"The Ruby Programming Language" has half a chapter where they work on a couple of additions to enumerable to make ruby look like Haskell.
You may also want to browse previous Stack Overflow questions tagged with both "Ruby" and "functional-programming" here.
回答6:
This is not pretty or efficient code. It is to explain the difference between a functional and non functional style.
Something you need to think about when doing functional concepts in ruby is that our vm is not built for it, so without transparent referential optimization, you WILL eat a lot more memory writing in a functional style.
# non functional
class Post
attr_accessor :title, :body
end
all_posts = []
# create a post
p = Post.new
p.title = "Hello world"
p.body = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
# put post in the array
all_posts << p
# fix post title
all_posts[0].title = "Opps, fixed title"
#functional
class Post
attr_reader :title, :body
def initialize(attrs={})
attrs.each {|k,v| instance_variable_set "@#{k.to_s}", v }
end
end
all_posts = []
# create a post
p = Post.new({title: "Hello world", body: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."})
# add to array via new array creation
all_posts = all_posts + [p] # be wary when all_posts is a very large collection!
old_post = p
p = Post.new({title: "Oops, fixed title", body: old_post.body})
all_posts = all_posts - [old_post] + [p]
I post on functional programming in ruby quite often at my blog: http://rubylove.io