functional-programmingf#raku

How can I make a functional chaining operator in Raku?


I a trying to translate this F# code to Raku for learning purposes:

type Meat = Chicken | Beef | Pork | Fish | Veggie

type Ingredient =
    | Cheese | Rice | Beans | Salsa | Guacamole | SourCream | Lettuce
    | Tomato | Onion | Cilantro | PicoDeGallo

type Burrito = Meat option * Ingredient list

let (>>=) burrito f =
    match burrito with
    | Some meat, ingredients -> f (Some meat, ingredients)
    | None, _ -> None, []

let returnBurrito (meat, ingredients) = meat, ingredients

let tortilla = returnBurrito (Some Veggie, [])

let addMeat meat (m, ingredients) = Some meat, ingredients

let addIngredient ingredient (meat, ingredients) =
    meat, ingredient :: ingredients

let addMissionBurritoIngredients (meat, ingredients) =
    meat, Cheese :: Rice :: Beans :: ingredients

let holdThe ingredient (meat, ingredients) =
    meat, List.filter (fun i -> i <> ingredient) ingredients

let burrito =
    tortilla
    >>= addMeat Chicken
    >>= addMissionBurritoIngredients
    >>= holdThe Cheese
    >>= addIngredient PicoDeGallo
    >>= addIngredient Salsa
    >>= addIngredient Guacamole
    >>= addIngredient SourCream

printfn "%A" burrito

So far, in Raku, I have this working code (but it is not generally chainable):

use Monad::Result;

enum Meat <Chicken Beef Pork Fish Veggie>;

enum Ingredient <Cheese Rice Beans Salsa Guacamole SourCream Lettuce Tomato Onion Cilantro PicoDeGallo>;

sub returnBurrito($meat, @ingredients) { $meat, @ingredients }

sub tortilla { returnBurrito( Monad::Result.ok(Veggie), [] ) }

sub add-meat($meat, ($, @ingredients)) { Monad::Result.ok($meat), @ingredients }

sub add-ingredient($ingredient, ($meat, @ingredients)) {
    $meat, [$ingredient, |@ingredients]
}

sub add-mission-burrito-ingredients(($meat, @ingredients)) {
    $meat, [Cheese, Rice, Beans, |@ingredients]
}

sub hold-the($ingredient, ($meat, @ingredients)) {
    ($meat, [@ingredients.grep(* != $ingredient)]);
}

my $burrito = tortilla;
say $burrito;

sub zz( $burrito, &f ) {
    -> $meat { &f( $meat, $burrito ) }
}

say zz($burrito, &add-meat);
say zz($burrito, &add-meat)(Beef);

The problem is that I cannot figure out how to make the chaining operation work as an infix.

I have tried this attempt:

sub infix:<xx>( $burrito, &f ) {
    -> $meat { &f( $meat, $burrito ) }
}

say ($burrito xx &add-meat);
say ($burrito xx &add-meat)(Beef);

But it gives the error:

(Ok ( Veggie ) [])
-> $meat { #`(Block|5635547097056) ... }
Cannot unpack or Capture `Code.new`.
To create a Capture, add parentheses: \(...)
If unpacking in a signature, perhaps you needlessly used parentheses? -> ($x) {} vs. -> $x {}
or missed `:` in signature unpacking? -> &c:(Int) {}
  in sub add-meat at burrito.raku line 11
  in block <unit> at burrito.raku line 29

Solution

  • Here is the corrected code that eliminates the use of xx as a placeholder...

    use Monad::Result;
    
    enum Meat <Chicken Beef Pork Fish Veggie>;
    
    enum Ingredient <Cheese Rice Beans Salsa Guacamole SourCream Lettuce Tomato Onion Cilantro PicoDeGallo>;
    
    sub returnBurrito($meat, @ingredients) { $meat, @ingredients }
    
    sub tortilla { returnBurrito( Monad::Result.ok(Veggie), [] ) }
    
    sub add-meat($meat, ($, @ingredients)) { Monad::Result.ok($meat), @ingredients }
    
    sub add-ingredient($ingredient, ($meat, @ingredients)) {
        $meat, [$ingredient, |@ingredients]
    }
    
    sub add-mission-burrito-ingredients(($meat, @ingredients)) {
        $meat, [Cheese, Rice, Beans, |@ingredients]
    }
    
    sub hold-the($ingredient, ($meat, @ingredients)) {
        ($meat, [@ingredients.grep(* != $ingredient)]);
    }
    
    sub infix:«>>=»( $burrito, &f ) {
        given $burrito {
            when *.is-ok,    $ {
                -> $arg=Empty { &f( |($arg, $burrito) ) }
            }
            when *.is-error, $ {
                (Monad::Result.error('None'), [])
            }
        }
    }
    
    my \burrito =
        (((((((tortilla()
            >>= &add-meat)(Chicken)
            >>= &add-mission-burrito-ingredients)()
            >>= &hold-the)(Cheese)
            >>= &add-ingredient)(PicoDeGallo)
            >>= &add-ingredient)(Salsa)
            >>= &add-ingredient)(Guacamole)
            >>= &add-ingredient)(SourCream)
    ;
    
    say burrito;
    #(Ok ( Chicken ) [SourCream Guacamole Salsa PicoDeGallo Rice Beans])
    

    Overall I think that this fairly quick translation of the F# is quite true to the original intent, although the use of Empty and the LISP style brackets in the summary are probably areas that could be done better with some more thought.