Is Haskell an acceptable Perl?

Post on 21-Jan-2018

697 views 1 download

Transcript of Is Haskell an acceptable Perl?

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 #1practical 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 #2real 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 Hierarchyuse 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 Hierarchyuse 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 programmesub 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 programmeuse experimental 'lexical_subs';

has list => (

is => 'ro',

isa => ArrayRef[InstanceOf["Programme"]],

handles_via => 'Array',

handles => { filter => 'grep'}

);

Search for a programmesearch 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