Haskell to Perl 6 - nutshell
Learning Perl 6 from Haskell, in a nutshell: what do I already know?
Haskell and Perl 6 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 Perl 6. The Haskell user may find that they need not abandon all of their Haskelly thoughts while scripting in Perl 6.
Note that this should not be mistaken for a beginner tutorial or overview of Perl 6; it is intended as a technical reference for Perl 6 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 -- TypesplusTwo x = x + 2 -- Values
You do not mix types and values in Haskell like the below
plusTwo 2 -- This is validplusTwo Integer -- This is not valid
In Perl 6, types (AKA type objects) live on the same level as values
sub plus-two(Int --> Int)plus-two(2); # This is validplus-two(Int); # This is valid
I will illustrate this unique aspect of Perl 6 with one more example:
multi sub is-string(Str $ --> True)multi sub is-string(Any $ --> False)is-string('hello'); #Trueis-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 Integercase parseInt myString ofJust x -> xNothing -> 0
In Perl 6, 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 --> Int)my = ;given parse-int()
So in Perl 6 we have type constraints that indicate the definedness of a type. These are
Int; # This is a defined Int.Int; # This is an undefined Int, AKA a type objectInt; # 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 --> Int)# One way to do itmy = ;given parse-int()# Another way to do itmy Int = parse-int();if .defined else# A better waywith parse-int() else# With the defined-or operatorparse-int() // 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, Perl 6 introduces andthen
and orelse
.
sub parse-int(Str --> Int)my = ;my = parse-int() orelse 0;sub hello()hello() andthen say 'bye';
So in practice, Perl 6 does not have the concept of a null type, but rather of defined or undefined types.
Data definitions
Perl 6 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 ymoveUp :: Point -> PointmoveUp (Point x y) = Point x (y + 1)
And an equivalent Perl 6 example:
sub move-up(Point --> Point)
The code I 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 Perl 6. The closest thing would be an Enum.
data Animal = Dog | Cat | Bird | HorsetestAnimal :: Animal -> StringtestAnimal Dog = "Woof"testAnimal Horse = "Neigh"
Although it does not fit the same exact use cases, it can be used in putting constraints on types.
< Dog Cat Bird Horse >;proto sub test-animal( Animal )multi sub test-animal( Dog )multi sub test-animal( Animal::Horse ) # more explicitsay test-animal Animal::Dog; # more explicitsay 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 = StringfullName :: Name -> Name -> NamefullName first last = first ++ last
The equivalent in Perl 6 is the following.
my constant Name = Str;sub full-name ( Name \first, Name \last --> Name )
It should be noted that in Perl 6, one can also create a subset of an existing type.
of Str where *.chars < 20;sub full-name(Name , Name )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 -> Stringgreeting "" = "Hello, World!"greeting "bub" = "Hey bub."greeting name = "Hello, " ++ name ++ "!"
Perl 6 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 )
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 .signature; # (Str \name --> Str)
An interesting thing to note in the Perl 6 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 belowmulti greeting(Str \name where '' )multi greeting(Str \name where 'bub' )# The above is the same as the below, again.multi greeting(Str \name where ~~ '' )multi greeting(Str \name where ~~ '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 )multi greeting(Str \name where -> )multi greeting ( Str \name where --> 'True' )multi greeting ( Str \name where )
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 --> 'True' )multi greeting ( Str \name where 'bub' --> 'Hey, bub.' )say greeting '' ; # will never say Truesay greeting 'bub'; # about 50% of the time it will say True
Argument
Deconstruction
TODO
Currying
TODO
.assuming vs currying
method chaining vs currying
Composing
TODO
show function composition operator. Maybe explain a more perl6ish way to do this though.
Case / matching
Haskell makes heavy use of case matching like the below:
case number of2 -> "two"4 -> "four"8 -> "eight"_ -> "don't care"
In Perl 6 you can achieve this same thing with the given/when structure:
my = ;given
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 difference between perl6 Array, Sequence, List. 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 Perl6. But rather, 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 Perl6 :
# using `if` and `for`my = ( if %% 2 for 0..100);# using gather/take to build a Seqmy = gather for 0..100 ;# using gather/take to build an Arraymy = gather for 0..100 ;
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 = map , 0..100;my = grep , 0..100;# using a Whatever lambdamy = 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 Perl6:
my = 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 Perl6: https://design.perl6.org/S04.html#The_do-once_loop.
As you can see, when you get into some more advanced Haskell list comprehensions, Perl6 does not translate exactly the same, but it's possible to do the same things, nonetheless.
Fold
Fold in Haskell is called Reduce in Perl 6.
mySum = foldl `+` 0 numList
my = ;reduce , 0, |;.reduce(, with => 0)
However, in Perl 6, if you want to use an infix operator (+ - / % etc) there is a nice little helper called the Reduction metaoperator.
my = ;[+] # This is the same[+] 0, # 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 Perl 6, this difference is determined by the associativity attached to the operator/subroutine.
sub two-elem-list ( \a, \b )# you can use a subroutine as an infix operatorsay 'a' [] 'b'; # (a b)# as the reduction prefix metaoperator takes an infix operator, it will work there too;[[]] 1..5; # ((((1 2) 3) 4) 5)say (1..5).reduce: ; # ((((1 2) 3) 4) 5)# right associativesub right-two-elem-list( \a, \b ) is assoc<right>say (1..5).reduce: ; # (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 assocsay [[]] 1..5;# chainingsay [<] 1..5; # Truesay (1..5).reduce: &[<]; # True
Map
TODO
Ranges
Haskell and Perl 6 both allow you to specify ranges of values.
myRange1 = 10..100myRange2 = 1.. -- InfinitemyRange3 = 'a'..'h' -- Letters work too
my = 10..100;my = 1..*; # Infinitemy = 'a'..'h'; # Letters work too
Laziness vs eagerness
In the examples above, you have the concept of laziness displayed very plainly. Perl 6 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 Perl 6 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