raku & rust: Option-Some-None

Regular visitors to my blog will know that I think raku and rust are both awesome in their chosen niches and are natural companions for the modern programming era just as perl and C were back in the day.

Coming off an excellent 2nd raku conference over the weekend, I got to thinking about how both languages handle the concept of “nothing” (the absence of a value) and wanted to have some -Ofun.

The rust idiom

First I googled some rust code example to remind me of the rust Option / Some / None idiom:

enum Option<T> {
    None,
    Some(T),
}

let mut opt: Option<i32>; 
opt = Some(1); 
//opt = None; 
match opt {
    Some(x) => {
        println!("Got {}", x); 
    }   
    None => {
        println!("Got nothing");
    }   
}

Here’s what the Rust Book has to say

Sometimes it’s desirable to catch the failure of some parts of a program instead of calling panic!; this can be accomplished using the Option enum.

The Option<T> enum has two variants:

  • None, to indicate failure or lack of value, and
  • Some(value), a tuple struct that wraps a value with type T.
https://doc.rust-lang.org/rust-by-example/std/option.html

The raku idiom

Then I compared to the usual raku idiom using Nil – of course since raku is gradually typed the most natural idiom for a quick 4 liner is no types.

my $opt;
$opt = 4;
#$opt = Nil;

with $opt { say "Got $_" } else { say "Got nothing" }

This may seem pretty cool and uncontroversial – but a lengthy debate “much ado about nothing” has been had over on github recently.

Which was enriched by a typically evocative insight from Larry Wall:

Nil is a kind of singularity. Black holes have no hair, and trying to fit them neatly back into the fabric of the universe results in contradictions. Similarly, attempting to fit Nil back into the normal type system is bound to cause problems, which is why we have Raku’s Nil rather than Perl’s undef. Perl programmers often fall into the trap of trying to store undef as a normal value, and get themselves into trouble down the road.

Black holes can also have an accretion disk (and maybe a “firewall”), which records some of the history of what is falling into the black hole. This is what a Failure does; it records some of the history of a singularity. The stuff still falling in is where black holes can keep their “hair”, at least temporarily.

The design of Nil with respect to Failure was primarily a cultural decision, not a technical decision. We do not want people thinking of Nil as a value, or as a type from which other user-defined types can be derived, other than user-defined Failures. We want them to think of Nil as the least-marked form of failure, so that they never treat Nil as some kind of nuanced success marker, or try to create a parallel system of nothingness that pretends to be storable as a value.

We could conceivably redefine Nil and Failure as both forms of Singularity (the property which subverts the return type system of the universe by the permanent absence of a return value), but the universe discourages people from creating their own singularities, and I suspect we should too.

Hope this helps… Larry

https://en.wikipedia.org/wiki/Larry_Wall

All your base are belong to us (the rust idiom in raku)

Not entirely content with this, I wondered if the raku (gradual) type system could do the more structured rust Option-Some-None thing. Here’s where I got to after a couple of hours hacking:

subset Option of Any where Some|None;

my Option $opt;
$opt = Some.new(7);
#$opt = None;
given $opt {
    when Some {
        say "Got {.v}"  
    }   
    when None {
        say "Got nothing"
    }   
}

Admittedly I only did Some[Int] via raku parametric roles and I should have followed suit with Option[Int] … but I am content to leave that as an exercise for the reader.

Here’s all the code to implement this with a few comments to show off the raku cool stuff:

role Things {                   #a utility Role for both flavours of Some

    method gist {
        "Some[{$.v.^name}]"     #
    }                           # .gist and .raku are used by .say and .Str
                                # methods ... so we override them to make nice
    method raku {               # output
        "Some[{$.v.^name}]"
    }

    method Str {             
        ~$.v                    # ~ is the Str concatenate operator, when used
    }                           # as a prefix it coerces its argument to (Str)

    method Numeric {
        +$.v                    # ~ is the addition operator, when used as a
    }                           # prefix it coerces its argument to (Num)
}

role None {}                    # roles are cool labels

role Some[::T] does Things {    # a parameterized role with a type capture
    has T $.v is required;      # using the type capture for a public attr

    multi method new( $v ) {    # ensure that the value is defined
        die "Died with undefined value" without $v;
        self.new: :$v
    }
}

role Some does Things {         # role are multis (Some[Int].new and Some.new)
    has $.s is required;        # require the attr ... if absent, fail
    has $.v;
    
    multi method new( ::T $v ) {   # use the type capture in a signature
        die "Died with undefined value" without $v; 
        self.new: s => Some[(T)].new(:$v)
    }   

    submethod TWEAK {           # late stage constructor alias $.v to $.v
        $!v := $!s.v            # for the Some Things role
    }   
}

#err - that's it!

### some tests...
my $x = Some[Int].new(42);
say "$x is ", $x;               # 42 is Some[Int]
say $x + 2;                     # 44 

my $y = Some[Rat].new(3.2);     # 3.2 is Some[Rat]
say "$y is ", $y; 

my $z = Some.new(2e1);
say "$z is ", $z;               # 20 is Some[Num]
say $z + 2e0;                   #22 

#my $a = Some.new();            #Died with X::Attribute::Required
#my $b = Some.new(Nil);         #Died with undefined value

This example shows how raku can be setup to ape another type system fairly closely in only 42 lines of code. Although you may have to resort to a bit more sleight of hand and effort to get rid of the .new method and to bring in Option[::T] in synch with Some[::T] – but not bad for a blog post.

Some of the things I like about raku that contribute to this are:

  • use of roles for composition
  • parameterized roles
  • type captures
  • auto punning (roles become concrete classes)
  • subsets where clauses

As usual, please comment here or over at the raku reddit page or the raku irc (also via discord) – let me know if any rustaceans would like to join in the -Ofun!

~p6steve

4 Comments

  1. p6steve says:

    Hi glfdex,

    Thanks for the comment … nice new repo ! I am calm ;-)… bringing your text here for others…

    <<>>

    I agree whole heartedly with your words and as stated my blog was for -Ofun. When I started with raku (perl6), I was sceptical about the whole idea of Gradual Types (is this the appropriate term, even?) … surely, I thought, unless types are comprehensive and mandatory everywhere, then they can be just sidestepped.

    But having played around with raku for a bit, I now feel that Gradual Types are a very important tool for making code comprehensible, testable and maintainable – even if any maintainer could just come in a subvert the types that are being used.

    This is a different source of value than, say Rust, where the type system is used to rigorously enforce safety and error free code. And the Rust kind of strong typing is very very important for watertight system code.

    Of course – this requires coders that are keen to make code work and who appreciate the design of the original author. [On the other hand I am pretty sure a black hat coder could defeat even Rust type safety eg. by misusing “`match _ =>“` or similar.]

    But I think I learned a few things by trying to ape the Rust Option type in raku … (i) that this is fairly possible and (ii) that coders that value the Option-Some-None paradigm (or some other paradigm) could fairly readily interpret these concepts in raku. Would anyone want to do that in real life – I doubt it

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s