Josh Reback
Josh Reback

Reputation: 579

First Class Functions In Ruby

I am working my way through this article on functional programming (https://codewords.recurse.com/issues/one/an-introduction-to-functional-programming) and trying to do the exercises in Ruby.

One exercise defines two functions, zero and one. Zero takes a string parameter and returns string index 1 - end if the first character is 0, One does the same thing but only if the first character is one.

Here are the ruby implementations:

def zero(s)
  if s[0] == "0"
    return s[1..(s.length)]
  end
end

def one(s)
  if s[0] == "1"
    return s[1..(s.length)]
  end
end

The problem asks you to write a method called rule_sequence that, given a string and an array of functions, returns the result such that the functions are invoked one at a time -- the first function is invoked in the whole string, the second function invoked on the return value of that string, etc. If, at any point, the one of the functions returns nil, return nil.

The Python implementation is:

def rule_sequence(s, rules):
    if s == None or not rules:
        return s
    else:
        return rule_sequence(rules[0](s), rules[1:])

However, since it doesn't seem like Ruby has support for higher order functions, the most elegant solution I could come up with was the following:

def rule_sequence(string, rules)
  if rules.length == 0 or string.nil?
    return string
  else
    return rule_sequence(rules[0].call(string), rules[1..rules.length])
  end
end

puts rule_sequence('0101', [lambda { |s| zero(s) }, lambda { |s| one(s) }, lambda { |s| zero(s) } ])

Can anyone come up with something more terse than passing or calling lambdas?

Upvotes: 5

Views: 2408

Answers (2)

erasing
erasing

Reputation: 526

I'll take this exercise as an opportunity to show how Ruby supports higher order functions.

Let's go one step back and rewrite the zero - one functions. You can notice they have quite a lot in common. Let's try to exploit that by writing a lambda that can generate both

tail_on_prefix = lambda {|prefix|
  lambda {|str| str[1..-1] if str[0] == prefix}
}

We can now easily define zero and one

zero = tail_on_prefix.("0")
one = tail_on_prefix.("1")

To rule_sequence now!

rule_sequence = lambda {|str, rules|
  if (str.nil? or rules.empty?)
    str
  else
    rule_sequence.(rules[0].(str), rules[1..-1])
  end
}

now calling rule_sequence looks a bit better, doesn't it

rule_sequence.("100101", [one, zero, zero]) # => "101"

Upvotes: 7

Alan Tam
Alan Tam

Reputation: 2057

You can do this

puts rule_sequence('0101', [method(:zero), method(:one), method(:zero)])

Just BTW, s[1..(s.length)] can be written as s[1..-1], and rules[0].call(string) can be written as rules[0].(string).

Upvotes: 3

Related Questions