Gem Version CI Coverage Status

Lab42::Curry

Name says it all..

Curry functions and methods at will, reorder, define placeholders anywhere, positional and named args

N.B. All these code examples are verified with the speculate_about gem

So what does it do?

Context Positional Parameters

The simplest and classical way to curry a function (I include methods when I say function) is by providing the first n parameters to a function needing m parameters and thusly defining a function that needs now m - n parameters.

Given such a simple funcion

    def adder(a, b, c); a + 10*b + 100*c end
    let(:add_to_1) {curry(:adder, 1)}
    # Equivalent to Elixir's &adder(1, &1, &2)

N.B. that Lab42::Curry has been included into Examples and ExampleGroups in spec/spec_helper.rb

Then very unsurprisingly:

    expect(add_to_1.(2, 3)).to eq(321)

We call the arguments passed into curry the compiletime arguments, and the arguments passed into the invocation of the curried function, which has been returned by the invocation of curry, the runtime arguments.

In our case the compiletime arguments were [1] and the runtime arguments were [2, 3]

Reordering

There are several methods of reordering arguments, the simplest is probably using placeholders.

When a placeholder is provided (Lab42::Curry.runtime_arg aliased as rt_arg )

    let(:add_to_30) { curry(:adder, rt_arg, 3) }
    # Equivalent to Elixir's &adder(&1, 3, &2)

Then we see that

    expect( add_to_30.(1, 5) ).to eq(531)

Total control over argument order...

... can be achieved by passing the index of the positional argument to be used into Lab42::Curry.runtime_arg

Given the total reorder form

    let(:twohundred_three) { curry(:adder, runtime_arg(2), runtime_arg(0), 1) }
    # now first argument is c (index 2) and second a (index 0) and b = 1
    # Like Elixir's &adder(&2, 1, &1)

Then we have

    expect( twohundred_three.(2, 3) ).to eq(213)

Picking a position for a compiletime argument

It might be cumbersome to write things like: curry(..., rt_arg, rt_arg, ..., rt_arg, 42)

Therefore we can express the same much more concisely with Lab42::Curry.compiletime_args, and its alias ct_args

Given

    let(:twohundred) { curry(:adder, ct_args(2 => 2)) }
    # same as curry(:adder, rt_arg, rt_arg, 2)

Then we get

    expect( twohundred.(4, 3) ).to eq(234)

N.B. that we could have defined add_to_30 as curry(:adder, rt_arg, 3, rt_arg) of course

Error Handling

When you indicate values for the same position multiple times Then the ArgumentCompiler saves you:

    expect{ curry(:adder, 1, ct_args(0 => 1)) }.to raise_error(Lab42::Curry::DuplicatePositionSpecification)

Context With proc like objects

Given a lambda

    let(:sub) { ->{ _1 - _2} }
    let(:inverse) { curry(sub, rt_arg(1), rt_arg) }

Then we will get the negative value

    expect( inverse.(2, 1) ).to eq(-1)

Context Keyword Arguments

Given a function which takes keyword arguments like the following

    def rectangle(length, width, border: 0, color: )
      [length, width, border, color]
    end
    let(:red_rectangle) { curry(:rectangle, color: :red) }
    let(:wide_bordered) { curry(:rectangle, rt_arg, 999, border: 1) }

Then the red rectangle gives us

    expect( red_rectangle.(1, 2) ).to eq([1, 2, 0, :red])
    expect( red_rectangle.(1, 2, border: 1) ).to eq([1, 2, 1, :red])

Can we override curried values, normally not Example: cannot override

    expect{ red_rectangle.(1, 2, color: :blue) }
      .to raise_error(
        Lab42::Curry::DuplicateKeywordArgument,
        "keyword argument :color is already defined with value :red cannot override with :blue")

But we can create a more lenient curry with curry!

  expect( curry!(:rectangle, 1, 2, color: :red ).(color: :blue) )
    .to eq([1, 2, 0, :blue])

LICENSE

Copyright 2020,1 Robert Dober [email protected]

Apache-2.0 c.f LICENSE