Haskell to Raku - nutshell

Learning Raku from Haskell, in a nutshell

Haskell and Raku are very different languages. This is obvious. However, that does not mean there are not similarities or shared ideas! This page attempts to get a Haskell user up and running with Raku. The Haskell user may find that they need not abandon all of their Haskelly thoughts while scripting in Raku.

Note that this should not be mistaken for a beginner tutorial or overview of Raku; it is intended as a technical reference for Raku learners with a strong Haskell background.

Types

Types vs values

In Haskell, you have type level programming and then value level programming.

plusTwo :: Integer -> Integer   -- Types
plusTwo x = x + 2               -- Values

You do not mix types and values in Haskell like the below

plusTwo 2          -- This is valid
plusTwo Integer    -- This is not valid

In Raku, types (AKA type objects) live on the same level as values

sub plus-two(Int $x --> Int) { $x + 2 }
plus-two(2);    # This is valid
plus-two(Int);  # This is valid

I will illustrate this unique aspect of Raku with one more example:

multi is-string(Str $ --> True) {}
multi is-string(Any $ --> False) {}
is-string('hello');    #True
is-string(4);          #False

Maybe

In Haskell, you have a Maybe type that allows you to forgo the worry of null types. Let's say you have a hypothetical function that parses a String to an Integer:

parseInt :: String -> Maybe Integer
case parseInt myString of
  Just x  -> x
  Nothing -> 0

In Raku, since type objects coexist with regular objects, we have the concept of Defined and Undefined objects. Plain type objects are undefined while instantiated objects are defined.

sub parse-int(Str $s --> Int) { ... }
my $string = {...};
given parse-int($string) {
  when Int:D { $_ }
  when Int:U { 0 }
}

So in Raku we have type constraints that indicate the definedness of a type. These are

Int:D; # This is a defined Int.
Int:U; # This is an undefined Int, AKA a type object
Int:_; # This is either defined or undefined.

If we wanted to be explicit in the above example (probably a good idea), we could add the :_ constraint on the return type. This would let the user know that they should account for both defined and undefined return values. We could also use other methods and constructs that specifically test for definedness.

sub parse-int(Str $s --> Int:_) { ... }
# One way to do it
my $string = {...};
given parse-int($string) {
  when Int:D { $_ }
  when Int:U { 0 }
}
# Another way to do it
my Int $number = parse-int($string);
if $number.defined { $number } else { 0 }
# A better way
with parse-int($string) { $_ } else { 0 }
# With the defined-or operator
parse-int($string) // 0

The with operator that you see above is like if, except it explicitly tests for definedness and then passes the result to the following block. Similarly, without tests that the object is undefined and also passes the result to the following block.

For more natural control flow with undefined and defined types, Raku introduces andthen and orelse.

sub parse-int(Str $s --> Int:_) { ... }
my $string = {...};
my $result = parse-int($string) orelse 0;
sub hello() { say 'hi' }
hello() andthen say 'bye';

So in practice, Raku does not have the concept of a null type, but rather of defined or undefined types.

Data definitions

Raku is fundamentally an object oriented language. However, it also gives you the freedom to write in virtually any paradigm you wish. If you only want to pure functions that take an object and return a new object, you can certainly do so.

Here is a Haskell code example:

data Point = Point x y
moveUp :: Point -> Point
moveUp (Point x y) = Point x (y + 1)

And an equivalent Raku example:

class Point { has $.x; has $.y; }
sub move-up(Point $p --> Point) {
  Point.new(x => $p.x, y => $p.y + 1)
}

The code illustrated above is an example of a Product Type. If instead you'd like to write a Sum Type, there is not an exact equivalent in Raku. The closest thing would be an Enum.

data Animal = Dog | Cat | Bird | Horse
testAnimal :: Animal -> String
testAnimal Dog   = "Woof"
testAnimal Horse = "Neigh"

Although it does not fit the same exact use cases, it can be used in putting constraints on types.

enum Animal < Dog Cat Bird Horse >;
proto test-animal( Animal        ) {*}
multi test-animal( Dog           ) { 'Woof' }
multi test-animal( Animal::Horse ) { 'Neigh'  }   # more explicit
say test-animal Animal::Dog;                          # more explicit
say test-animal Horse;

Type aliases and subsets

In Haskell, you can alias an existing type to simply increase clarity of intent and re-use existing types.

type Name = String
fullName :: Name -> Name -> Name
fullName first last = first ++ last

The equivalent in Raku is the following.

my constant Name = Str;
sub full-name ( Name \first, Name \last --> Name ) { first ~ last }

It should be noted that in Raku, one can also create a subset of an existing type.

subset Name of Str where *.chars < 20;
sub full-name(Name $first, Name $last) {
  $first ~ $last
}
full-name("12345678901234567890111", "Smith") # This does not compile, as the first parameter
                                              # doesn't fit the Name type

Typeclasses

TODO

Functions

Definitions and signatures

Pattern Matching

Haskell makes heavy use of pattern matching in function definitions.

greeting :: String -> String
greeting  ""   = "Hello, World!"
greeting "bub" = "Hey bub."
greeting  name = "Hello, " ++ name ++ "!"

Raku does this as well! You just use the multi keyword to signify that it is a multiple dispatch function.

proto greeting ( Str   --> Str ) {*}
multi greeting ( ""    --> "Hello, World!" ) {}
multi greeting ( "bub" --> "Hey bub." ) {}
multi greeting ( \name ) { "Hello, " ~ name ~ "!" }

The proto declarator is not necessary, but can sometimes aid in making sure that all multis follow your business rules. Using a variable name in the signature of the proto would provide more information in error messages, and for introspection.

proto greeting ( Str \name --> Str ) {*}
say &greeting.signature;                  # OUTPUT: «(Str \name --> Str)␤»

An interesting thing to note in the Raku code above is that passing values like 'bub' as a function parameter is just syntax sugar for a where guard.

Guards

Using the example from the "Pattern Matching" section of this page, you can see the guards that are used behind the scenes to constrain our function arguments.

multi greeting ( ""    --> "Hello, World!" ) {}
multi greeting ( "bub" --> "Hey bub." ) {}
# The above is the same as the below
multi greeting(Str \name where ''    ) {'Hello, World!'}
multi greeting(Str \name where 'bub' ) {'Hey bub.'}
# The above is the same as the below, again.
multi greeting(Str \name where $_ ~~ ''   ) {'Hello, World!'}
multi greeting(Str \name where $_ ~~ 'bub') {'Hey bub.'}

$_ is known as the topic variable. It assumes the form of whatever is appropriate. The smartmatch operator ~~ figures out the best way to determine if the left matches the right, be it number ranges, strings, etc. Our three examples above go from most sugared (top), to least sugared (bottom).

The bottom examples above could be wrapped in curly braces, making it more obvious that it is a code block. Note that a where clause may also take an explicit Callable.

multi greeting(Str \name where { $_ ~~ '' } ) {'Hello, World!'}
multi greeting(Str \name where -> $thing { $thing ~~ '' } ) {'Hello, World!'}
multi greeting ( Str \name where { Bool.pick } --> 'True' ){}
multi greeting ( Str \name where &some-subroutine ){…}

If you read the section in this page on subsets, you'll notice that "where" is used in the making of subsets as well as here. The usage of "where" in both areas is exactly the same.

When using where, note that the order of definition is important, just like in Haskell.

multi greeting ( Str \name where '' --> 'Hello, World!' ){}
multi greeting ( Str \name where { Bool.pick } --> 'True' ){}
multi greeting ( Str \name where 'bub' --> 'Hey, bub.' ){}
say greeting ''   ; # will never say True
say greeting 'bub'; # about 50% of the time it will say True

Argument Deconstruction

TODO

Currying / Partial Application

Haskell functions are in curried form by default and as such they allow partial application directly.

plus : Int -> Int -> Int
plus a b = a + b
plusTwo = plus 2

In raku, partial application can be achieved by assuming method on any Callable object.

sub plus(Int $i, Int $j --> Int) { return $i + $j; }
my &plus-two = &plus.assuming(2, *);

Composing

TODO

show function composition operator. Maybe explain a more native Raku way to do this though.

Case / matching

Haskell makes heavy use of case matching like the below:

case number of
  2 -> "two"
  4 -> "four"
  8 -> "eight"
  _ -> "don't care"

In Raku you can achieve this same thing with the given/when structure:

my $number = {...};
given $number {
  when 2  { "two" }
  when 4  { "four" }
  when 8  { "eight" }
  default { "don't care" }
}

Note that the order of the when's is also significant, just like with the where's in the guard section of this page.

Lists

TODO

explain differences between Raku Arrays, Sequences, and Lists. Explain data shapes in regards to the @ sigil. Explain how you can convert an Array to a flattened list of objects with |@

data shapes become quite intuitive, but it takes a bit of practice.

List comprehensions

There are no explicit list comprehensions in Raku. But you can achieve list comprehensions a couple of different ways.

Here is a trivial example in Haskell:

evens = [ x | x <- [0..100], even x ]

And now in Raku:

# using `if` and `for`
my @evens = ($_ if $_ %% 2 for 0..100);
# using gather/take to build a Seq
my $evens = gather for 0..100 { take $_ if $_ %% 2 };
# using gather/take to build an Array
my @evens = gather for 0..100 { take $_ if $_ %% 2 };

Since for is always eager it is generally better to use map or grep which will inherit the laziness or eagerness of its list argument.

my @evens = map { $_ if $_ %% 2 }, 0..100;
my @evens = grep { $_ %% 2 }, 0..100;
# using a Whatever lambda
my @evens = grep  * %% 2,  0..100;

Here is the creation of tuples in Haskell:

tuples = [(i,j) | i <- [1,2],
                  j <- [1..4] ]
-- [(1,1),(1,2),(1,3),(1,4),(2,1),(2,2),(2,3),(2,4)]

And in Raku:

my @tuples = 1,2  X  1..4;
# [(1,1), (1,2), (1,3), (1,4), (2,1), (2,2), (2,3), (2,4)]

See this design document for more information on what kinds of list comprehensions are possible in: https://design.raku.org/S04.html#The_do-once_loop.

As you can see, when you get into some more advanced Haskell list comprehensions, Raku does not translate exactly the same, but it's possible to do the same things, nonetheless.

Fold

Fold in Haskell is called Reduce in Raku.

mySum = foldl (+) 0 numList
my @numbers = {...};
reduce { $^a + $^b }, 0, |@numbers;
@numbers.reduce: {$^a + $^b}

However, in Raku, if you want to use an infix operator (+ - / % etc) there is a nice little helper called the Reduction metaoperator.

my @numbers = {...};
[+] @numbers     # This is the same
[+] 0, |@numbers # as this

It inserts the operator in between all values in the list and produces a result, just like Fold.

In Haskell you, you have foldl and foldr. In Raku, this difference is determined by the associativity attached to the operator/subroutine.

sub two-elem-list ( \a, \b ) { ( a, b ) }
# you can use a subroutine as an infix operator
say 'a' [&two-elem-list] 'b'; # OUTPUT: «(a b)␤»
# as the reduction prefix metaoperator takes an infix operator, it will work there too;
[[&two-elem-list]] 1..5;           # OUTPUT: «((((1 2) 3) 4) 5)␤»
say (1..5).reduce: &two-elem-list; # OUTPUT: «((((1 2) 3) 4) 5)␤»
# right associative
sub right-two-elem-list( \a, \b ) is assoc<right> { ( a, b ) }
say (1..5).reduce: &right-two-elem-list; # OUTPUT: «(1 (2 (3 (4 5))))␤»
# XXX there is possibly a bug here as this currently doesn't look at
# XXX the associativity of &right-two-elem-list and just always does left assoc
say [[&right-two-elem-list]] 1..5;
# chaining
say [<] 1..5;            # OUTPUT: «True␤»
say (1..5).reduce: &[<]; # OUTPUT: «True␤»

takeWhile

The takeWhile function in Haskell runs over a list returning all elements until a condition is met:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
takeWhile (<20) fibs -- Returns [0,1,1,2,3,5,8,13]

There's no single equivalent function in Raku; several alternatives have been proposed in the issue that originated this text. This would be one of the alternatives:

[1, 2, 3, 40, 50, 60, 7, 8, 9] ...^ !(* < 10)

Although it's not a single function, it's essentially a specific way of using the sequence operator using the caret for excluding the last term, and using a condition for ending the sequence. This specific example would be equivalent to

takeWhile (<10) [1, 2, 3, 40, 50, 60, 7, 8, 9]

Map

TODO

Ranges

Haskell and Raku both allow you to specify ranges of values.

myRange1 = 10..100
myRange2 = 1..        -- Infinite
myRange3 = 'a'..'h'   -- Letters work too
my $range1 = 10..100;
my $range2 = 1..*;      # Infinite
my $range3 = 'a'..'h';  # Letters work too

Laziness vs eagerness

In the examples above, you have the concept of laziness displayed very plainly. Raku has laziness only where it makes the most sense. For example, in the range 10..100, this is eager because it has a definite end. If a list does not have a definite end, then the list should clearly be lazy.

(1 .. 100).is-lazy; # False
(1 .. Inf).is-lazy; # True

These are the "sane defaults" that Raku takes pride in. But they are still defaults and can be changed into one or the other.

(1 .. 100).lazy.is-lazy;       # True
(1 .. 100).lazy.eager.is-lazy; # False

Contexts (let-in / where)

TODO

explain how given/when and with/without and for loops open lexical scopes with the argument as the context.

compare it to let/in and where constructs maybe?

Parsers

Parser combinators vs grammars

TODO

Tail Call Optimization or Tail Call Elimination

Haskell and many other functional programming languages use tail call optimization, also sometimes called tail call elimination, to remove the stack overhead of some types of recursive function calls.

There is nothing in the Raku language specification forbidding the implementation of this class of optimization, but no current implementation has it.

Please note that many Haskell looping constructs use recursive function calls. Haskell programs would encounter stack overflow errors more often without tail call optimization. The standard Raku looping constructs are not built on recursive function calls, which makes the feature less important.

See Also

Perl to Raku guide - in a nutshell

How do I do what I used to do? (Raku in a nutshell)

Perl to Raku guide - overview

How do I do what I used to do?

Perl to Raku guide - functions

Builtin functions in Perl to Raku

Perl to Raku guide - operators

Operators in Perl to Raku: equivalencies and variations

Perl to Raku guide - syntax

Syntactic differences between Perl and Raku

Perl to Raku guide - special variables

A comparison of special variables in Perl and Raku

JavaScript (Node.js) to Raku - nutshell

Learning Raku from Node.js, in a nutshell

Python to Raku - nutshell

Learning Raku from Python, in a nutshell

Ruby to Raku - nutshell

Learning Raku from Ruby, in a nutshell: what do I already know?

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.