1

I'm exploring Functional Programming with the Ruby language. Below is my version of a Fold in Ruby. I've tested it on a variety of functions, reverse, filter, map etc, and it returns the results as expected. But it mutates data and needs assignment statements. Can anyone help me to do the same but without violating the Functional paradigm? Can anyone help me with the partial application of the curried function at the bottom? I suspect there something obvious I'm missing. Thanks.

fold_l = lambda do |ray, base, funcky|
    if ray == []
        base
    else
        base = funcky.call(base,ray.first)
        ray.shift
        fold_l.call(ray,base,funcky)
    end
end

abc = [1, 2, 3, 4, 5, 6, 7]
mapper = lambda {|sum, x| sum << x*x}
lengthy = lambda {|sum, _| sum + 1}

p fold_l.call(abc,[],mapper)  ## works fine
p abc                         ## but mutates data!!
abc = [1, 2, 3, 4, 5, 6, 7]

p curryFold = fold_l.curry.(abc).(0).(lengthy) ## works fine
lengthC = curryFold.(base:0).(funcky:lengthy)  
p lengthC.call.(abc)  ## but this gives error

2 Answers2

2

Rework your fold_l function to not mangle the arguments its given:

def fold_l(ray, base, funcky)
  return base if ray.empty?

  base = funcky.call(base,ray.first)

  fold_l(ray.last(ray.length-1),base,funcky)
end

This uses last to return a copy of the arguments minus the first. It's also not necessary to use lambda here as you want a named function, so you may as well declare it formally. lambda is reserved for situations where you don't necessarily have a name for it.

Note that in Ruby it's generally rude to damage the arguments your method's given unless there's an understanding that it's acceptable. Most methods make copies if they need to perform alterations.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Thanks for your help in using 'last', and I note that the 'base = funcky.call(base,ray.first)' part can be tucked into the second parameter of the fold_l invocation to avoid the assignment statement. The currying is still a problem. Following your suggestions, I now get an error on BOTH statements... – Martin Trueman Oct 11 '17 at 17:29
  • If this solves your problem of mangling arguments then it's a solution to the original issue. If you have additional problems it's worth opening that up as a new question that's focused on that particular angle. – tadman Oct 11 '17 at 18:07
  • 1
    Not only is it "rude to damage the arguments" (nice wordsmithing!), but it is my understanding that a basic tenet of functional programming is that code must not have side-effects, such as the mutation of arguments. – Cary Swoveland Oct 11 '17 at 19:36
1

I would probably implement foldl like this – and always be careful when using recursion in languages that don't support tail call optimisation (read more)

foldl = -> (f, acc, (x,*xs)) do
  if x.nil? then
    acc
  else
    foldl.call f, (f.call acc, x), xs
  end
end

add = -> (x,y) do
  x + y
end

length =
  foldl.curry
    . (-> (acc,_) { acc + 1 })
    . (0) 

data = [ 1, 2, 3, 4, 5 ]

p foldl.call add, 0, data
# => 15

p length.call data
# => 5

as per @tadman's recommendation, using a normal def/end block is probably better but that's just a matter of preference – note that currying is no longer necessary with this style

def foldl f, acc, (x,*xs)
  if x.nil? then
    acc
  else
    foldl f, (f.call acc, x), xs
  end
end

def add x, y
  x + y
end

def length xs
  foldl (-> (acc,_) { acc + 1 }), 0, xs
end

data = [ 1, 2, 3, 4, 5 ]

p foldl method(:add), 0, data
# => 15

p length data
# => 5

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Thanks for your answer. I liked the '(x, *xs)' trick, though Ruby syntax didn't make it any easier to figure out. I needed 4 nested parens in the argument to get a small sample to run ie: def sample((x,*xs)) – Martin Trueman Oct 13 '17 at 00:56