Is Haskell an Acceptable Perl? @osfameron LambdaLounge MadLab 18 April 2016 https://github.com/osfameron/haskell-perl
Overview Perl: https://www.perl.org/ Perl 5 is a highly capable, feature-rich programming language with over 27 years of development Haskell: https://www.haskell.org/ An advanced purely- functional programming language An open source community effort for over 20 years
Received Wisdom? Perl: dynamic messy simple flexible Haskell: static clean hard mathsy
Received Wisdom? Perl: dynamic messy simple flexible $?=~s/(?:[%^]+)/a/gsmx; Haskell: static clean hard mathsy a>>=(c@(_ _)->b’$c:[d])
Comparison Perl: Imperative OO Functional “Weakly typed” Real World Haskell: - (or best?) - Purely Functional Strongly typed ?
Comparison Perl: Imperative OO Functional “Weakly typed” Real World “whipuptitude” Haskell: - (or best?) - Purely Functional Strongly typed ? “manipulexity”
“Around 1993 I started reading books about Lisp, and I discovered something important: Perl is much more like Lisp than it is like C.”
What Makes Lisp Different? “…describes seven features of Lisp. Perl shares six of these features; C shares none of them. These are big, important features, features like first- class functions, dynamic access to the symbol table, and automatic storage management.” -- mjd
Perl6: 1st implementation (Pugs) written in Haskell! Many Haskell ideas influenced design of Perl6.
GHC: Used Perl to prototype early versions of key components (the Evil Mangler, etc.) still required for “boot” script to build ghc from source
Example: ??? (“Annie”, “Bob”, “Chandra”) => (5, 3, 7)
Example: map ??? (“Annie”, “Bob”, “Chandra”) => (5, 3, 7)
Example: map length, (“Annie”, “Bob”, “Chandra”) => (5, 3, 7)
Perl vs Haskell? ??? map length, (“Annie”, “Bob”, “Chandra”)
Map map length, (“Annie”, “Bob”, “Chandra”) map length [“Annie”, “Bob”, “Chandra”]
Map map length, (“Annie”, “Bob”, “Chandra”) map length [“Annie”, “Bob”, “Chandra”] length :: String -> Int length “Annie” => 5 map length :: [String]->[Int] map :: (a -> b) -> [a] -> [b]
Map map length, (“Annie”, “Bob”, “Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) map length [“Annie”, “Bob”, “Chandra”]
Map map length, (“Annie”, “Bob”, “Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) map length [“Annie”, “Bob”, “Chandra”]
Map map length, (“Annie”, “Bob”, “Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) # length EXPR # length # If EXPR is omitted, returns the length of $_ map length [“Annie”, “Bob”, “Chandra”]
Map map length, (“Annie”, “Bob”, “Chandra”) • expression • $_ (“it”) • optional parameters map length [“Annie”, “Bob”, “Chandra”] • function • currying • “lifting”
Doubling map $_ * 2, (1..5) => (2,4,6,8,10) map (*2) [1..5] => [2,4,6,8,10]
Doubling map double($_), (1..5) use experimental ‘signatures’; sub double ($x) { $x * 2 } map double [1..5] double x = x * 2 -- or double = (*2)
Define our own ‘fmap’ fmap &double, (1..5) sub double ($x) { $x * 2 } sub fmap ??? map double [1..5] double x = x * 2 -- or double = (*2)
Define our own ‘fmap’ fmap &double, (1..5) sub double ($x) { $x * 2 } sub fmap ($fn, @list) { map $fn->($_), @list } map double [1..5] double x = x * 2 -- or double = (*2)
Prototypes... map _&double, (1..5) sub double ($x) { $x * 2 } { no experimental 'signatures'; sub _ ($) { shift->($_) } } map double [1..5] double x = x * 2 -- or double = (*2)
Reverse... ??? map reverse [“foo”, “bar”] => [“oof”, “rab”
Reverse... map reverse, (“foo”, “bar”) => ... map reverse [“foo”, “bar”] => [“oof”, “rab”
Reverse... map reverse, (“foo”, “bar”) => () # wtf? map reverse [“foo”, “bar”] => [“oof”, “rab”
Reverse... map reverse, (“foo”, “bar”) => () # reverse LIST reverse (“foo”, “bar”) => (“bar”, “foo”) reverse () => () # reverse STRING reverse “foo” => “oof” # reverse ($_) map reverse [“foo”, “bar”] => [“oof”, “rab”
Reverse... map scalar reverse, (“foo”, “bar”) => (“oof”, “rab”) map reverse [“foo”, “bar”] => [“oof”, “rab”
Reverse... map scalar reverse, (“foo”, “bar”) => (“oof”, “rab”) • map is really concatMap! map reverse [“foo”, “bar”] => [“oof”, “rab”
concatMap vs map map {$_,$_} (1..3) => (1,1,2,2,3,3) map (x->[x,x]) [1..3] => [[1,1],[2,2],[3,3]]
concatMap vs map map {$_,$_} (1..3) => (1,1,2,2,3,3) concatMap (x->[x,x]) [1..3] => [1,1,2,2,3,3]
Reverse with concatMap map scalar reverse, (“foo”, “bar”) => (“oof”, “rab”) concatMap reverse [“foo”, “bar”] => [“oofrab”] -- eeek!
Reverse with concatMap map scalar reverse, (“foo”, “bar”) => (“oof”, “rab”) • scalar vs list context concatMap (return . reverse) [“foo”, “bar”] => [“oof”, “rab”] • map vs concatMap • return (to wrap value in e.g. list)
Length of lists of lists: ??? ([1,2,3], [5,4,3,2,1]) => (3, 5)
Length of LoL l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Length of LoL my @l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # wtf l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Length of LoL my @l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # length STRING [1,2,3] => “ARRAY(0x293a1d8)” l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Length of LoL my @l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # length STRING [1,2,3] => “ARRAY(0x293a1d8)” # scalar @array l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Length of LoL my @l = ([1,2,3], [5,4,3,2,1]); map scalar, @l => ([1,2,3], [5,4,3,2,1]) # [1,2,3] is already scalar! l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Length of LoL my @l = ([1,2,3], [5,4,3,2,1]); map scalar @$_, @l => (3, 5) l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
Tails ??? map tail l => [[2,3], [4,3,2,1]]
Tails map [ @$_[1..$#$_] ], @l => ([2,3], [4,3,2,1]) map tail l => [[2,3], [4,3,2,1]]
Tails map [ @$_[1..$#$_] ], @l => ([2,3], [4,3,2,1]) • Yes, I know map tail l => [[2,3], [4,3,2,1]]
Tails map tail($_), @l => () # bah! sub tail ($head, @tail) { @tail } map tail l => [[2,3], [4,3,2,1]]
Tails map __&tail, @l => ([2,3], [4,3,2,1]) sub tail ($head, @tail) { @tail } { no experimental 'signatures'; sub _ ($) { shift->($_) } sub __ ($) { [shift->(@$_)] } } map tail l => [[2,3], [4,3,2,1]]
A thought... Scalar/list context Sigils ($, @, %) implicit $_ Optional parameters References and dereferencing Automatic type coercions Subroutine prototypes Type system
Example Scalar/list context Monads (Maybe, List)
Example: -- https://prime.haskell.org/wiki/Libraries/Proposals/MonadFail import qualified Data.Map as M en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
Example: *Main> translate en2it "hello" "ciao" *Main> translate en2it "hello" :: Maybe String Just "ciao" *Main> translate en2it "hello" :: [String] ["ciao"] *Main> translate en2it "whipuptitude" *** Exception: user error (No translation) *Main> translate en2it "whipuptitude" :: Maybe String Nothing *Main> translate en2it "whipuptitude" :: [String] []
To be fair... Perl: • Larry Wall: linguist, missionary • + jobbing programmers • pragmatic • magpie-like Haskell: • Decades of Comp Sci professors and PhD students • purity • experimentation
Why are we* so terrified of types? en2it :: M.Map [Char] [Char] en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate :: (Monad m, Ord k) => M.Map k a -> k -> m a translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
Why are we* so terrified of types? en2it :: M.Map [Char] [Char] en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate :: (Monad m, Ord k) => M.Map k a -> k -> m a translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
Why are we* so terrified of types? “Programming in ML is very pleasant. More than almost any other language I know, you can just write down the definitions of the functions as they come into your head. You don't need to bother with declarations; everything is just figured out for you automatically. And you do get a lot of type errors, both actual failures and also places where the type emitted by the compiler is not what you thought it should be. But unlike in C or Pascal, every one of those errors indicates a real, serious problem in your program, not merely something you have to groan over and try to work around the compiler's stupidity yet again.” -- mjd http://perl.plover.com/classes/typing/notes.html
Why are we* so terrified of types? [4, 8, 15, 16, 23, 42] :: [Int]
Type terror: undefined values [4, 8, 15, undef, 16, 23, 42] :: [Panic!]
Type terror: undefined values my %en2it = ( hello => “ciao”, goodbye => “ciao”, pasta => “pasta” ); my @l = map $en2it{$_}, (“hello", “monad", “pasta”); my @c = grep defined, @l; # (“ciao”, “pasta”)
Type terror: undefined values -- data Maybe a = Just a | Nothing l = map (flip Map.lookup h) [“hello", “monad", “pasta”] -- [Just “ciao”, Nothing, Just “pasta”] c = catMaybes l -- [“ciao”, “pasta”]
Type terror: signal values [1, undef, 2, “X”, 3, -5] # positive number: record # undef: no record found # “string”: processing instruction # negative number: record flagged for deletion
Type terror: signal values records :: [Maybe Record] records = [ Just Record (Left 1), Nothing, Just Record (Left 2), Just Instruction “X” Just Record (Left 2), Just Record (Right 5)] data Record = Record (Either Int Int) | Instruction String
Type terror: but! but! but! serialization (show) objects of a particular class (typeclasses) GADTs existential types dynamic introspection ...
Bonus section #1 practical oneliners
april.txt There is shadow under this red rock, (Come in under the shadow of this red rock), And I will show you something different from either Your shadow at morning striding behind you Or your shadow at evening rising to meet you; I will show you fear in a handful of dust.
Grep for a string $ perl -ne ‘print if /shadow/’ < april.txt ???
Grep for a string $ perl -ne ‘print if /shadow/’ < april.txt notaoneliner.hs: import Data.List main = interact ( unlines . (filter (a -> "shadow" `isInfixOf` a)) . lines ) $ runghc notaoneliner.hs < april.txt
april.txt | grep shadow There is shadow under this red rock, (Come in under the shadow of this red rock), Your shadow at morning striding behind you Or your shadow at evening rising to meet you;
Grep for a string $ perl -ne ‘print if /shadow/’ < april.txt hask.bash: if which ghc > /dev/null then function h { ghc -e "interact ($*)" Ust.hs ; } function hl { h "bylines ($*)" ; } function hw { h "bywords ($*)" ; } fi $ hl ‘filter (regexBool “shadow”)’ < april.txt
Ust.hs https://wiki.haskell.org/Simple_Unix_tools https://ulissesaraujo.wordpress.com/tag/command/ {-# LANGUAGE NoMonomorphismRestriction #-} module Ust( bylines, bywords, showln, regexBool, uniq, rpt, take', drop', head', tail', tail10, tac, rev, rev_w, wc_c, wc_l, wc_w, space, unspace, remove, upper, clean, clean', clean'', blank, join, tr, tr_d, grep, grep_v, cksum ) where import Control.Monad.Instances; import Data.List; import Data.Char; import Data.Maybe; import Text.Printf; import System.Environment; import Text.Regex.Posix -- First, three helpers bylines f = (unlines . f . lines) bywords f = (unwords . f . words) showln = (++ "n") . show -- simple boolean regex matching regexBool r l = l =~ r :: Bool ...
Uppercase $ perl -pe ‘$_ = uc’ < april.txt $ h upper < april.txt Ust.hs: upper = map toUpper
april.txt | upper THERE IS SHADOW UNDER THIS RED ROCK, (COME IN UNDER THE SHADOW OF THIS RED ROCK), AND I WILL SHOW YOU SOMETHING DIFFERENT FROM EITHER YOUR SHADOW AT MORNING STRIDING BEHIND YOU OR YOUR SHADOW AT EVENING RISING TO MEET YOU; I WILL SHOW YOU FEAR IN A HANDFUL OF DUST.
Sort $ perl -e 'print sort <>' < april.txt $ hl sort < april.txt
april.txt | sort (Come in under the shadow of this red rock), And I will show you something different from either I will show you fear in a handful of dust. Or your shadow at evening rising to meet you; There is shadow under this red rock, Your shadow at morning striding behind you
Sorted words $ perl -e 'print join " ", sort map { chomp; split } <>' < april.txt $ hw sort < april.txt
april.txt | sort-words (Come And I I Or There Your a at at behind different dust. either evening fear from handful in in is meet morning of of red red rising rock), rock, shadow shadow shadow shadow show show something striding the this this to under under will will you you you you; your
Bonus section #2 real world(ish) code
Modelling TV programmes Brand: “Have I Got News For You?” Series: “3” Episode: “1” / HIGNFY123
Modelling TV programmes Brand: “Have I Got News For You?” Series: “3” Episode: “1” / HIGNFY123 Brand: “Have I Got OLD News For You?” Series: “1” Episode: “5” / HIGNFY123
Data modelling package Programme; use Moo; use Types::Standard qw/ Maybe Str /; has id => ( is => 'ro', isa => Maybe[Str] ); has id2 => ( is => 'ro', isa => Maybe[Str] ); package Brand; use Moo; extends 'Programme'; # ditto Series, Episode... type PrimaryId = Maybe String type AlternateId = Maybe String data Prog = Prog ProgType PrimaryId AlternateId deriving (Eq, Ord, Show) data ProgType = Brand | Series | Episode deriving (Eq, Ord, Show)
The “Database” package Database; use Moo; use Types::Standard qw/ArrayRef HashRef InstanceOf/; has list => ( is => 'ro', isa => ArrayRef[InstanceOf["Programme"]], ... ); has graph => ( is => 'ro', isa => HashRef[InstanceOf["Programme"]], ... ); data Database = Database { list :: [Prog], graph :: M.Map Prog Prog }
Get Hierarchy use experimental ‘signatures’; sub getHierarchy($self, $prog) { if (my $parent = $self->lookup($prog->id)) { return ($prog, $self->getHierarchy($parent)) } else { return $prog } } getHierarchy :: Database -> Prog -> [Prog] getHierarchy db p = let l = M.lookup p (graph db) in case l of Nothing -> [p] Just (parent) -> p : (getHierarchy db parent)
Get Hierarchy use experimental ‘signatures’; use MooX::HandlesVia; has graph => ( is => 'ro', isa => HashRef[InstanceOf["Programme"]], handles_via => 'Hash', handles => { lookup => ‘get’ } ); sub getHierarchy($self, $prog) { if (my $parent = $self->lookup($prog->id)) { return ($prog, $self->getHierarchy($parent)) } else { return $prog } }
Search for a programme sub search ($self, $prog1) { my sub match ($prog2) { return unless ref $prog1 eq ref $prog2; return unless $prog1->id or $prog1->id2; return unless $prog2->id or $prog2->id2; return unless $prog1->id or $prog2->id2; return unless $prog1->id2 or $prog2->id; return if $prog1->id and $prog2->id and $prog1->id ne $prog2->id; return if $prog1->id2 and $prog2->id2 and $prog1->id2 ne $prog2->id2; return 1; } return $self->filter(&match); }
Search for a programme use experimental 'lexical_subs'; has list => ( is => 'ro', isa => ArrayRef[InstanceOf["Programme"]], handles_via => 'Array', handles => { filter => 'grep'} );
Search for a programme search db p = filter (match p) (list db) where match (Prog t1 _ _) (Prog t2 _ _) | t1 /= t2 = False match (Prog _ Nothing Nothing) _ = False match _ (Prog _ Nothing Nothing) = False match (Prog _ Nothing _) (Prog _ _ Nothing) = False match (Prog _ _ Nothing) (Prog _ Nothing _) = False match (Prog _ (Just p1) _) (Prog _ (Just p2) _) | p1 /= p2 = False match (Prog _ _ (Just a1)) (Prog _ _ (Just a2)) | a1 /= a2 = False match _ _ = True
Thanks! @osfameron https://github.com/osfameron/haskell-perl

Is Haskell an acceptable Perl?

  • 1.
    Is Haskell an AcceptablePerl? @osfameron LambdaLounge MadLab 18 April 2016 https://github.com/osfameron/haskell-perl
  • 2.
    Overview Perl: https://www.perl.org/ Perl 5 isa highly capable, feature-rich programming language with over 27 years of development Haskell: https://www.haskell.org/ An advanced purely- functional programming language An open source community effort for over 20 years
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
    “Around 1993 I startedreading books about Lisp, and I discovered something important: Perl is much more like Lisp than it is like C.”
  • 8.
    What Makes Lisp Different? “…describesseven features of Lisp. Perl shares six of these features; C shares none of them. These are big, important features, features like first- class functions, dynamic access to the symbol table, and automatic storage management.” -- mjd
  • 9.
    Perl6: 1st implementation (Pugs) writtenin Haskell! Many Haskell ideas influenced design of Perl6.
  • 10.
    GHC: Used Perl to prototypeearly versions of key components (the Evil Mangler, etc.) still required for “boot” script to build ghc from source
  • 12.
    Example: ??? (“Annie”, “Bob”,“Chandra”) => (5, 3, 7)
  • 13.
    Example: map ??? (“Annie”,“Bob”, “Chandra”) => (5, 3, 7)
  • 14.
    Example: map length, (“Annie”,“Bob”, “Chandra”) => (5, 3, 7)
  • 15.
    Perl vs Haskell? ???map length, (“Annie”, “Bob”, “Chandra”)
  • 16.
    Map map length, (“Annie”, “Bob”,“Chandra”) map length [“Annie”, “Bob”, “Chandra”]
  • 17.
    Map map length, (“Annie”, “Bob”,“Chandra”) map length [“Annie”, “Bob”, “Chandra”] length :: String -> Int length “Annie” => 5 map length :: [String]->[Int] map :: (a -> b) -> [a] -> [b]
  • 18.
    Map map length, (“Annie”, “Bob”,“Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) map length [“Annie”, “Bob”, “Chandra”]
  • 19.
    Map map length, (“Annie”, “Bob”,“Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) map length [“Annie”, “Bob”, “Chandra”]
  • 20.
    Map map length, (“Annie”, “Bob”,“Chandra”) # map EXPR, LIST # map BLOCK LIST map { length } (…) # length EXPR # length # If EXPR is omitted, returns the length of $_ map length [“Annie”, “Bob”, “Chandra”]
  • 21.
    Map map length, (“Annie”, “Bob”,“Chandra”) • expression • $_ (“it”) • optional parameters map length [“Annie”, “Bob”, “Chandra”] • function • currying • “lifting”
  • 22.
    Doubling map $_ *2, (1..5) => (2,4,6,8,10) map (*2) [1..5] => [2,4,6,8,10]
  • 23.
    Doubling map double($_), (1..5) useexperimental ‘signatures’; sub double ($x) { $x * 2 } map double [1..5] double x = x * 2 -- or double = (*2)
  • 24.
    Define our own‘fmap’ fmap &double, (1..5) sub double ($x) { $x * 2 } sub fmap ??? map double [1..5] double x = x * 2 -- or double = (*2)
  • 25.
    Define our own‘fmap’ fmap &double, (1..5) sub double ($x) { $x * 2 } sub fmap ($fn, @list) { map $fn->($_), @list } map double [1..5] double x = x * 2 -- or double = (*2)
  • 26.
    Prototypes... map _&double, (1..5) subdouble ($x) { $x * 2 } { no experimental 'signatures'; sub _ ($) { shift->($_) } } map double [1..5] double x = x * 2 -- or double = (*2)
  • 27.
    Reverse... ??? map reverse [“foo”,“bar”] => [“oof”, “rab”
  • 28.
    Reverse... map reverse, (“foo”, “bar”) =>... map reverse [“foo”, “bar”] => [“oof”, “rab”
  • 29.
    Reverse... map reverse, (“foo”, “bar”) =>() # wtf? map reverse [“foo”, “bar”] => [“oof”, “rab”
  • 30.
    Reverse... map reverse, (“foo”, “bar”) =>() # reverse LIST reverse (“foo”, “bar”) => (“bar”, “foo”) reverse () => () # reverse STRING reverse “foo” => “oof” # reverse ($_) map reverse [“foo”, “bar”] => [“oof”, “rab”
  • 31.
    Reverse... map scalar reverse, (“foo”,“bar”) => (“oof”, “rab”) map reverse [“foo”, “bar”] => [“oof”, “rab”
  • 32.
    Reverse... map scalar reverse, (“foo”,“bar”) => (“oof”, “rab”) • map is really concatMap! map reverse [“foo”, “bar”] => [“oof”, “rab”
  • 33.
    concatMap vs map map{$_,$_} (1..3) => (1,1,2,2,3,3) map (x->[x,x]) [1..3] => [[1,1],[2,2],[3,3]]
  • 34.
    concatMap vs map map{$_,$_} (1..3) => (1,1,2,2,3,3) concatMap (x->[x,x]) [1..3] => [1,1,2,2,3,3]
  • 35.
    Reverse with concatMap mapscalar reverse, (“foo”, “bar”) => (“oof”, “rab”) concatMap reverse [“foo”, “bar”] => [“oofrab”] -- eeek!
  • 36.
    Reverse with concatMap mapscalar reverse, (“foo”, “bar”) => (“oof”, “rab”) • scalar vs list context concatMap (return . reverse) [“foo”, “bar”] => [“oof”, “rab”] • map vs concatMap • return (to wrap value in e.g. list)
  • 37.
    Length of listsof lists: ??? ([1,2,3], [5,4,3,2,1]) => (3, 5)
  • 38.
    Length of LoL l= [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 39.
    Length of LoL my@l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # wtf l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 40.
    Length of LoL my@l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # length STRING [1,2,3] => “ARRAY(0x293a1d8)” l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 41.
    Length of LoL my@l = ([1,2,3], [5,4,3,2,1]); map length, @l => (16, 16) # length STRING [1,2,3] => “ARRAY(0x293a1d8)” # scalar @array l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 42.
    Length of LoL my@l = ([1,2,3], [5,4,3,2,1]); map scalar, @l => ([1,2,3], [5,4,3,2,1]) # [1,2,3] is already scalar! l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 43.
    Length of LoL my@l = ([1,2,3], [5,4,3,2,1]); map scalar @$_, @l => (3, 5) l = [[1,2,3], [5,4,3,2,1]] map length l => [3, 5] length :: [a] -> Int
  • 44.
    Tails ??? map taill => [[2,3], [4,3,2,1]]
  • 45.
    Tails map [ @$_[1..$#$_]], @l => ([2,3], [4,3,2,1]) map tail l => [[2,3], [4,3,2,1]]
  • 46.
    Tails map [ @$_[1..$#$_]], @l => ([2,3], [4,3,2,1]) • Yes, I know map tail l => [[2,3], [4,3,2,1]]
  • 47.
    Tails map tail($_), @l =>() # bah! sub tail ($head, @tail) { @tail } map tail l => [[2,3], [4,3,2,1]]
  • 48.
    Tails map __&tail, @l =>([2,3], [4,3,2,1]) sub tail ($head, @tail) { @tail } { no experimental 'signatures'; sub _ ($) { shift->($_) } sub __ ($) { [shift->(@$_)] } } map tail l => [[2,3], [4,3,2,1]]
  • 49.
    A thought... Scalar/list context Sigils($, @, %) implicit $_ Optional parameters References and dereferencing Automatic type coercions Subroutine prototypes Type system
  • 50.
  • 51.
    Example: -- https://prime.haskell.org/wiki/Libraries/Proposals/MonadFail import qualifiedData.Map as M en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
  • 52.
    Example: *Main> translate en2it"hello" "ciao" *Main> translate en2it "hello" :: Maybe String Just "ciao" *Main> translate en2it "hello" :: [String] ["ciao"] *Main> translate en2it "whipuptitude" *** Exception: user error (No translation) *Main> translate en2it "whipuptitude" :: Maybe String Nothing *Main> translate en2it "whipuptitude" :: [String] []
  • 53.
    To be fair... Perl: •Larry Wall: linguist, missionary • + jobbing programmers • pragmatic • magpie-like Haskell: • Decades of Comp Sci professors and PhD students • purity • experimentation
  • 54.
    Why are we*so terrified of types? en2it :: M.Map [Char] [Char] en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate :: (Monad m, Ord k) => M.Map k a -> k -> m a translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
  • 55.
    Why are we*so terrified of types? en2it :: M.Map [Char] [Char] en2it = M.fromList [ ("hello","ciao"), ("goodbye","ciao"), ("pasta","pasta") ] translate :: (Monad m, Ord k) => M.Map k a -> k -> m a translate db k = let v = M.lookup k db in case v of Nothing -> fail "No translation" Just v' -> return v'
  • 56.
    Why are we*so terrified of types? “Programming in ML is very pleasant. More than almost any other language I know, you can just write down the definitions of the functions as they come into your head. You don't need to bother with declarations; everything is just figured out for you automatically. And you do get a lot of type errors, both actual failures and also places where the type emitted by the compiler is not what you thought it should be. But unlike in C or Pascal, every one of those errors indicates a real, serious problem in your program, not merely something you have to groan over and try to work around the compiler's stupidity yet again.” -- mjd http://perl.plover.com/classes/typing/notes.html
  • 57.
    Why are we*so terrified of types? [4, 8, 15, 16, 23, 42] :: [Int]
  • 58.
    Type terror: undefinedvalues [4, 8, 15, undef, 16, 23, 42] :: [Panic!]
  • 59.
    Type terror: undefinedvalues my %en2it = ( hello => “ciao”, goodbye => “ciao”, pasta => “pasta” ); my @l = map $en2it{$_}, (“hello", “monad", “pasta”); my @c = grep defined, @l; # (“ciao”, “pasta”)
  • 60.
    Type terror: undefinedvalues -- data Maybe a = Just a | Nothing l = map (flip Map.lookup h) [“hello", “monad", “pasta”] -- [Just “ciao”, Nothing, Just “pasta”] c = catMaybes l -- [“ciao”, “pasta”]
  • 61.
    Type terror: signalvalues [1, undef, 2, “X”, 3, -5] # positive number: record # undef: no record found # “string”: processing instruction # negative number: record flagged for deletion
  • 62.
    Type terror: signalvalues records :: [Maybe Record] records = [ Just Record (Left 1), Nothing, Just Record (Left 2), Just Instruction “X” Just Record (Left 2), Just Record (Right 5)] data Record = Record (Either Int Int) | Instruction String
  • 63.
    Type terror: but!but! but! serialization (show) objects of a particular class (typeclasses) GADTs existential types dynamic introspection ...
  • 64.
  • 65.
    april.txt There is shadowunder this red rock, (Come in under the shadow of this red rock), And I will show you something different from either Your shadow at morning striding behind you Or your shadow at evening rising to meet you; I will show you fear in a handful of dust.
  • 66.
    Grep for astring $ perl -ne ‘print if /shadow/’ < april.txt ???
  • 67.
    Grep for astring $ perl -ne ‘print if /shadow/’ < april.txt notaoneliner.hs: import Data.List main = interact ( unlines . (filter (a -> "shadow" `isInfixOf` a)) . lines ) $ runghc notaoneliner.hs < april.txt
  • 68.
    april.txt | grepshadow There is shadow under this red rock, (Come in under the shadow of this red rock), Your shadow at morning striding behind you Or your shadow at evening rising to meet you;
  • 69.
    Grep for astring $ perl -ne ‘print if /shadow/’ < april.txt hask.bash: if which ghc > /dev/null then function h { ghc -e "interact ($*)" Ust.hs ; } function hl { h "bylines ($*)" ; } function hw { h "bywords ($*)" ; } fi $ hl ‘filter (regexBool “shadow”)’ < april.txt
  • 70.
    Ust.hs https://wiki.haskell.org/Simple_Unix_tools https://ulissesaraujo.wordpress.com/tag/command/ {-# LANGUAGE NoMonomorphismRestriction#-} module Ust( bylines, bywords, showln, regexBool, uniq, rpt, take', drop', head', tail', tail10, tac, rev, rev_w, wc_c, wc_l, wc_w, space, unspace, remove, upper, clean, clean', clean'', blank, join, tr, tr_d, grep, grep_v, cksum ) where import Control.Monad.Instances; import Data.List; import Data.Char; import Data.Maybe; import Text.Printf; import System.Environment; import Text.Regex.Posix -- First, three helpers bylines f = (unlines . f . lines) bywords f = (unwords . f . words) showln = (++ "n") . show -- simple boolean regex matching regexBool r l = l =~ r :: Bool ...
  • 71.
    Uppercase $ perl -pe‘$_ = uc’ < april.txt $ h upper < april.txt Ust.hs: upper = map toUpper
  • 72.
    april.txt | upper THEREIS SHADOW UNDER THIS RED ROCK, (COME IN UNDER THE SHADOW OF THIS RED ROCK), AND I WILL SHOW YOU SOMETHING DIFFERENT FROM EITHER YOUR SHADOW AT MORNING STRIDING BEHIND YOU OR YOUR SHADOW AT EVENING RISING TO MEET YOU; I WILL SHOW YOU FEAR IN A HANDFUL OF DUST.
  • 73.
    Sort $ perl -e'print sort <>' < april.txt $ hl sort < april.txt
  • 74.
    april.txt | sort (Comein under the shadow of this red rock), And I will show you something different from either I will show you fear in a handful of dust. Or your shadow at evening rising to meet you; There is shadow under this red rock, Your shadow at morning striding behind you
  • 75.
    Sorted words $ perl-e 'print join " ", sort map { chomp; split } <>' < april.txt $ hw sort < april.txt
  • 76.
    april.txt | sort-words (ComeAnd I I Or There Your a at at behind different dust. either evening fear from handful in in is meet morning of of red red rising rock), rock, shadow shadow shadow shadow show show something striding the this this to under under will will you you you you; your
  • 77.
    Bonus section #2 realworld(ish) code
  • 78.
    Modelling TV programmes Brand: “HaveI Got News For You?” Series: “3” Episode: “1” / HIGNFY123
  • 79.
    Modelling TV programmes Brand: “HaveI Got News For You?” Series: “3” Episode: “1” / HIGNFY123 Brand: “Have I Got OLD News For You?” Series: “1” Episode: “5” / HIGNFY123
  • 80.
    Data modelling package Programme; useMoo; use Types::Standard qw/ Maybe Str /; has id => ( is => 'ro', isa => Maybe[Str] ); has id2 => ( is => 'ro', isa => Maybe[Str] ); package Brand; use Moo; extends 'Programme'; # ditto Series, Episode... type PrimaryId = Maybe String type AlternateId = Maybe String data Prog = Prog ProgType PrimaryId AlternateId deriving (Eq, Ord, Show) data ProgType = Brand | Series | Episode deriving (Eq, Ord, Show)
  • 81.
    The “Database” package Database; useMoo; use Types::Standard qw/ArrayRef HashRef InstanceOf/; has list => ( is => 'ro', isa => ArrayRef[InstanceOf["Programme"]], ... ); has graph => ( is => 'ro', isa => HashRef[InstanceOf["Programme"]], ... ); data Database = Database { list :: [Prog], graph :: M.Map Prog Prog }
  • 82.
    Get Hierarchy use experimental‘signatures’; sub getHierarchy($self, $prog) { if (my $parent = $self->lookup($prog->id)) { return ($prog, $self->getHierarchy($parent)) } else { return $prog } } getHierarchy :: Database -> Prog -> [Prog] getHierarchy db p = let l = M.lookup p (graph db) in case l of Nothing -> [p] Just (parent) -> p : (getHierarchy db parent)
  • 83.
    Get Hierarchy use experimental‘signatures’; use MooX::HandlesVia; has graph => ( is => 'ro', isa => HashRef[InstanceOf["Programme"]], handles_via => 'Hash', handles => { lookup => ‘get’ } ); sub getHierarchy($self, $prog) { if (my $parent = $self->lookup($prog->id)) { return ($prog, $self->getHierarchy($parent)) } else { return $prog } }
  • 84.
    Search for aprogramme sub search ($self, $prog1) { my sub match ($prog2) { return unless ref $prog1 eq ref $prog2; return unless $prog1->id or $prog1->id2; return unless $prog2->id or $prog2->id2; return unless $prog1->id or $prog2->id2; return unless $prog1->id2 or $prog2->id; return if $prog1->id and $prog2->id and $prog1->id ne $prog2->id; return if $prog1->id2 and $prog2->id2 and $prog1->id2 ne $prog2->id2; return 1; } return $self->filter(&match); }
  • 85.
    Search for aprogramme use experimental 'lexical_subs'; has list => ( is => 'ro', isa => ArrayRef[InstanceOf["Programme"]], handles_via => 'Array', handles => { filter => 'grep'} );
  • 86.
    Search for aprogramme search db p = filter (match p) (list db) where match (Prog t1 _ _) (Prog t2 _ _) | t1 /= t2 = False match (Prog _ Nothing Nothing) _ = False match _ (Prog _ Nothing Nothing) = False match (Prog _ Nothing _) (Prog _ _ Nothing) = False match (Prog _ _ Nothing) (Prog _ Nothing _) = False match (Prog _ (Just p1) _) (Prog _ (Just p2) _) | p1 /= p2 = False match (Prog _ _ (Just a1)) (Prog _ _ (Just a2)) | a1 /= a2 = False match _ _ = True
  • 87.