Watch Me Learn Haskell
I use some good software written in Haskell (Xmonad, Pandoc, ...) and, keen for a new adventure, I delved into it.
The community provides great help for newcomers:
- brief Typeclassopedia OOP_vs_type_classes Monads_as_computation Lists_and_strings FFI (wiki)
- tutorial syntax haskell2010 do (onlinereport)
- How to write a Haskell program
- user_guide
- reference
- Truth_values, arrows (wikibooks)
- learnyouahaskell
- realworldhaskell
- schoolofhaskell
- WIWIKWLH
- learnxinyminutes
- Awesome Haskell
- types
- haskell-by-types
- cheatsheet
- riptutorial
- extensions
- rosetta code
- Write Yourself a Scheme in 48 Hours
Mind-setting
To mum you can say: Make me a cake, please. To your sister you may need to say: Open the lowest drawer. Take the deep bowl. ...
Programming can be easy and it can be hard. A computer does know very little by itself. So it is hard to tell it what to do. The hard side is to organize, what you need to say to the computer.
The words and the way to organize things can vary between programming languages.
Variable vs Type
Variable comes from "vary", which is the Latin-rooted word for change.
Why a value changes does not matter for the concept of variable,
- along (no causality) or due to (causality) space or time or color or whatever
- by itself (no causality) or by choosing or selection (causality)
Variable is synonymous to alternatives or choice.
I walk. I run.
Both have the "I". This makes "walk" and "run" the values of this variable.
So variable arises due to a common context in which
- values exclude each other
- values can be selected individually
- values can be distinguished
Each of these mean the same. But lets settle with 1, with exclude (exclusive), to characterize (the values of) a variable.
What is a variable in one context is a value in another.
There is a history of various wording with similar to same meaning. variable-value is same as or similar to
- a-the
- level of indirection
- set-element (set theory)
- type-term (type theory)
The modelling in mathematics/software involves many levels of indirection. variable-value is a general concept that links the layers.
Type is the aspect that maps the real variable to the representation variable in a computer. Type as the implementation. Type refers to the
- amount of memory used for a variable and
- the encoding of the values of the variable
The choice of type is restricted by
- the requirements of the real variable and
- the types offered by the specific computer (language)
In Haskell you can specify the type separately, also later. If you omit it, Haskell will normally make a choice for you.
:{
v::Double
= 1
v :}
v
When you program you map a real variable hierarchy to a type hierarchy.
- In C-like languages the type describes a memory area (plus methods in OOP). A variable is an instance of such a type, i.e. a memory location with a specific address. Assignment is placing a value (a bit combination) to the memory area. The name identifies the typed memory area, the variable. This is static typing.
- In Python or R, the name identifies a value. The name is a variable in the sense that it can address different values of whatever type. The type associated with a name can change during run time. Assignment is basically naming. This is also called dynamic typing. If there are typing problems, this is only found during runtime (duck typing).
Functional languages are also statically or dynamically typed. Haskell is statically typed, Scheme and Clojure are dynamically typed.
Variable vs Function
Values normally are not random, but occur due to values of other variables. Value occurrences are a function of values from other variables.
Variable-value thus becomes function-(function application). function application is the selection of the value. This is the smallest building block of computing. Computing consists of (execution of) functions, i.e. the mapping from variable to variable.
All values of variables do occur (are exhaustive). The function f is all the couples f={(v,w)|vin V land win W land text{unique}(w)}.
In Haskell this is:
import Data.Char
:{
1 = 11
f 2 = 22
f | x >= 10 = digitToInt (head (show x))
f x :}
23
f 2
f 0 -- Exception: ... : Non-exhaustive patterns in function f f
Variables are more fundamental than functions, because you need to have choice first. The function maps this choice, the independent variable(s), to the target variable.
The function does not completely define the target variable, if not surjective. If not surjective the target variable might arise from more functions. The target variable would thus motivate a variable of functions towards it.
The function looses information, if not injective. Then, a common target value links source values, i.e. it produces a topological structure in the source variable.
There are also relations between variables that are not functions, i.e. that are not unique in either direction. Functional description can be restored by introducing new structure variables whose values combine original values according relation.
This produces complexity applicable only for specific contexts and does not have the generality needed in programming. Programming is about choosing, about the values.
Category theory avoids the complexity by not looking at internals: A well defined object gets mapped to another object or itself (id
) by a morphism. Morphisms need to be composable associatively (a path uniquely defines the target object).
I use variable instead of set to emphasizing that the important quantity is the exclusive choice the variable allows (the value).
In Haskell the choice is done by a data
construction. There can be more data constructors for one type. This allows to use different data layout within one type, while still being statically type checked (ADT).
The object in Category theory could be the value or the variable. The former is a dynamic variable (immutable), the latter is static variable (mutable). The Haskell types are static, but the variables are dynamic.
In Haskell the =
is a mapping rather than an assignment. Every application generates a new variable. Every generated value is associated with new memory allocation. To avoid that in critical code, Haskell also has mutable types.
Function composition
Haskell allows to compose functions without mentioning the arguments. This is called pointfree style, as values in mathematics are often called points. No argument values means no points. Ironically the usual composition operator is the point (.)
.
-- pointfree = sum . sequence [(**2) . sin, (**2) . cos] sc 2 -- 1.0 sc -- in this case better: = (sin x)**2 + (cos x)**2 sc x
pointfree is only shorter, if the return value is forwarded to the next function. For other situations there are other function compositions. Functions composing other function are called higher order functions or combinators.
In Haskell a lot of effort goes into the design of function compositions (Monad, Kleisli, Arrow, ...) to allow the elegant pointfree style.
The fundamental building block of computing is function application (selection), but immediately next in importance is how to compose them in a widely applicable way.
Wide application means good abstraction. Abstraction is compression. Compression means coping with less resources, less space, less time, less code, less energy. So effort is well spent, if it allows describing something in a more compressed way.
Functional Programming
Programming is based on mathematics, which is older than computers. We encounter variable-value, functions, etc. in all languages, but especially functional languages like Haskell push you to think mathematically. Code reuse demands abstraction. A good programmer needs to think abstractly, mathematically.
Many languages assume and work on an outside world. This outside world gives instructions their meaning. The "open the lowest drawer" example assumes a kitchen, which can be changed. One can open a drawer, etc ...
A purely functional style describes everything as functions. A function maps input to output without changing the input. In our example, a kitchen would be input and a kitchen with an open drawer would be output.
An output becomes a new input to another function. This function composition produces a time sequence, a natural thread of execution. If there were more cooks (more threads), they would all develop their own kitchens. No coordination needed, which makes things a lot easier. (In Haskell the kitchen would be a Monad.)
A programming style is a way to organize things. Languages can be used for more styles, but their syntax and libraries favor a specific style. A style that is shared in a community is called a paradigm.
Most people are first introduced to languages that favor an imperative style.
- Functions in non-functional languages change memory. They have side effects. Some languages call functions more appropriately "procedure" or "subroutine".
- Functions in functional programming languages don't change anything. They only map values to other values. They are mathematical functions.
The functional style passes functions around, instead of data.
Haskell tries hard to make you think purely functionally.
Syntax
BNF-syntax of Haskell: BNF
Syntax described by template Haskell: TH
Typing
A simple function type (signature) is:
fun:: Int -> Double
Unlike in C
or Java
this is a function without side effects, which makes it easier to test.
Not only types, but also variables of types (kind) are possible:
fun:: a -> b
:kind (->)
(->) :: * -> * -> *
->
accepts all type (*
= all types). ->
maps from two kinds (input) to a third kind (output). ->
has other usages as well.
Application is done via a space: fun some_value
. There are different types of applications:
- application of function
- application of constructor
- application of constraint
A constructor constructs a type. It is like a function signature without implementation, that can be applied to actual argument values, though. Since it cannot map the actual arguments, it just holds them. Therefore it is like a record in DB jargon, or a struct
in C
.
The implementation for the signature fun:: a -> b
would be fun pat = rhs
.
pat
could be just a letter, e.g.x
, which is a variable for any actual argument value during application.- Or
pat
could be a constrained pattern to address contained variables likex:xs
orAConstructor x
.
The rhs
is the last entry in the function type definition. The expression for rhs
depends only on the lhs arguments (e.g. on x
). Within the code of rhs
further functions with variables can be declared.
Via this containment of functions, context is built.
If the rhs
introduces new variables, the application of a function is the application of context.
Currying: fun
application is like walking along a path between variables. A (partial) walk on the path (a section), i.e. partial application, produces a function, that walks the rest of the path.
flip
or infix notation allows to curry also on the second argument.
Many functions in Haskell are of higher orders. Higher order functions combine (compose) functions to new functions (combinators) without the need to mention the variables.
In:
. ) :: (b -> c) -> (a -> b) -> a -> c (
( . )
has two lhs arguments(b -> c)
and(a -> b)
match functions
When applying ( . )
you don't need to mention the variables of type a, b, ...
.
In:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
f
is constrained to theApplicative
class.- The constraint between
::
and=>
is called Context. - The actual
f
must be a data type that is instance ofApplicative
and cannot be a single function. f
with space is a pattern for an application. Here it is a constructor application for the type implementingApplicative
.f (a -> b)
is the pattern for the first argument to extractf, a, b
.f a
is the pattern for the second argument.- The last argument
f b
is the type of the return value.
In:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>)
has two lhs arguments(a -> b)
is the pattern for the first argument: a function.f a
is a constructor pattern for the second argument: a data type.f
stands for a class (= variable of types = kind)Functor
is a class.Functor f
constrainsf
to types with theFunctor
class
The implementation of (<$>)
would construct a value using an actual f
constructor.
[]
is a type, which implements both, Applicative
and Functor
.
Usage:
*3), (*6) ] <*> [3]
[ (*) <$> [ 3, 6]) <*> [3]
((-- -> [9,18]
In Haskell a lot of typing is done via function signatures:
functions :: signature
class
is more signatures (interface)- a
data
ornewtype
type can be madeinstance
of more classes
:{
data ABType = ABType
class AClass a where
afun:: a -> a
class BClass b where
bfun:: b -> b
instance AClass ABType where
= id
afun instance BClass ABType where
= id
bfun fun:: ABType -> Int
= 1 -- just to make the compiler happy
fun ab :}
id
is the Haskell function for identity- Type and class names must start with capital letter.
ABType
is a type constrained to two classes:
fun:: ABType -> Int
is equivalent to:
fun:: (AClass ab, BClass ab) => ab -> Int -- Int is a type
Actually using (AClass a, BClass b) =>
would need the FlexibleContexts extension.
Int
is a type that is constraint to these (type) classes:
:info Int
type Int :: *
data Int = GHC.Types.I# GHC.Prim.Int#
-- Defined in ‘GHC.Types’
instance Eq Int -- Defined in ‘GHC.Classes’
instance Ord Int -- Defined in ‘GHC.Classes’
instance Enum Int -- Defined in ‘GHC.Enum’
instance Num Int -- Defined in ‘GHC.Num’
instance Real Int -- Defined in ‘GHC.Real’
instance Show Int -- Defined in ‘GHC.Show’
instance Read Int -- Defined in ‘GHC.Read’
instance Bounded Int -- Defined in ‘GHC.Enum’
instance Integral Int -- Defined in ‘GHC.Real’
Keywords
The top level declarations, ordered by importance, are:
<gendecl> | <fundecl> | data | instance | class | module | newtype | type | default
gendecl
: Function signature (fun ::
) or fixity.fundecl
: Functions use no keyword (read from left to right)data, type, newtype
are data related (read from right to left)class, instance
are type relatedmodule, default
are organizational
Keyword meaning:
module .. where
is used to specify what is exported by a file, thenwhere
and the details followdefault(Int)
or used in extensions, like DefaultSignaturesdata atype = rhs
introduces a type name that on the right hand side has possibly more constructor namesnewtype Key = Int
similar to data, but only one constructor allowed, which is seen by the compiler, but not in runtimetype Key = Int
creates a type synonym for the user, which is not seen by the compilerclass <Aclass> <params> where
is a container of function signaturesinstance <Aclass> <atype> where
declares an implementation of aclass
for a type. Implementation can be done automatically usingderiving
.
Data
data
can have named values (enum):
data Move = Walk | Run
let move = Walk
Walk = 5
speed Run = 10
speed
:t speed
-- -> speed :: Num p => Move -> p
Constructors Walk, Run
map to a type (Move
). Literals have a type. Haskell can infer the function signature.
Note the difference between type (data,newtype,type
) and constraint (class,instance
):
- type (
Move
here) is directly used in the signature p
is constrained to classNum
, which is more general, than using typeInt
orDouble
.
Constructors can be parametrized:
data Person = Person String Int deriving (Show)
let guy = Person "Buddy" 44
The parameters (fields) can be named, but actually it is naming the accessor function.
data Person = Person { nickname :: String, age :: Int} deriving (Show)
let guy1 = Person "Buddy" 44
let guy2 = Person { nickname = "Jo", age = 33}
nickname guy2-- -> "Jo"
= age guy2 + 1}
guy2 { age -- -> Person {nickname = "Jo", age = 34}
data
with one constructor and more fields is called a record.
data Shape location size = Rectangle location size | Circle location size deriving Show
:t Rectangle
-- -> Rectangle :: location -> size -> Shape location size
data Size = Small | Medium | Large deriving Show
data Location = Inside | Outside deriving Show
let ri = Rectangle Inside
:t ri
-- -> ri :: size -> Shape Location size
let ris = ri Small
:t ris
You cannot do Shape Inside Small
, because ambiguous.
Different data constructors (rhs
) are grouped by the common type constructor (lhs
). This is called algebraic data type (ADT).
data
can use recursion.
Code
An example
data Speed = Slow | Fast
data Move s = Walk s | Run s
:{
speed:: Num a => Move Speed -> a
Walk Slow) = 5
speed (Walk Fast) = 10
speed (Run Slow) = 11
speed (Run Fast) = 15
speed (:}
Run Fast)
speed (-- -> 15
:t speed
-- -> speed :: Num a => Move Speed -> a
lhs
Function: One or more declarations that map from the left-hand-side (lhs
) to the right-hand-side (rhs
).
'
can be part of a function name. Combinations of !#$%&*+./<=>?@\^-~|
and Unicode symbols can be used as function symbols (fop
).
Every lhs = rhs
has its own namespace. So never consider the argument naming when comparing two (related) declarations, because it just confuses you, if you see the same name for unrelated things.
lhs can be infix:
`fun` pat = rhs
pat = rhs pat fop pat
Or prefix:
= rhs
fun pat = rhs (fop) pat
lhs
can contain guards (|). There can be a where
at the end of the guards:
-- in ghci :{:} is needed
:{
|a<0 = a-1
aad a|a>0 = a+1
aad a|otherwise = a
aad a:}
-- equivalent to
:{
|a<0 = a-1
aad a|a>0 = a+1
|otherwise = a
:}
-1) -- use () with negative numbers
aad (-- -> -2
1
aad -- -> 2
0
aad -- -> 0
lhs
can contain patterns with sub-patterns (pat
). Patterns are built of:
_
(Constructor _) -- brackets is a good idea!
n@(Constructor _) -- rhs uses n
[a]
(x:xs)
!pat -- match now, not lazily
~pat -- always match (irrefutable), if you know it to succeed
n, a, x, xs
are arbitrary names that can be used in the rhs
. Constructor refers to an actual constructor. _
is anything.
Patterns are evaluated lazily by default. Lazy can mean a lot of memory consumption. It evaluates until the first constructor is found and then needs to remember the arguments (thunks) before trying other evaluation paths. Using !
avoids that.
rhs
The rhs declaration is an expression (exp
) with helper declarations either before:
= let ... in exp fun pat
or after:
= exp where
fun pat ...
The helper declarations can be in layout style:
... where
recl1...
declN
or
where {decl1;...;declN}
where
can also be used in class
and instance
declarations.
exp
is application of functions
fun a b
ora `fun` b
or(fop) a b
ora fop b
. To name functions with symbols (fop
) is normal in Haskell.fun $ pat
avoids brackets by reducing fixity to 0 (see:info $
)fun $! pat
evaluatespat
before applyingfun
Fixity of an operation is set with infixl|infixr|infix <fixity> <fop>
.
:{
:xs) y = fsum xs $! (x+y) -- same as: (x+y) `seq` fsum xs
fsum (x= y
fsum [] y :}
1..100] 0 fsum [
These can use patterns on the left side:
=
is a mapping<-
names values from a generator->
replaces=
in local scopes (e.g lambda\x -> x*x
)
Some other operators:
==
and/=
mean equal or not equal- \ introduces a lambda function (function without name)
:
prepend element in a list (1:[2]
)|
is a guard, used in declarations and list comprehensions..
generates a sequence of values based on a partial sequence.
module.sub-module or, with spaces, composes functions
let s = [x*x | x <- [1, 3 .. 9]]
!! 2
s -- -> 25
zip [1 ..] s
-- -> [(1,1),(2,9),(3,25),(4,49),(5,81)]
take 3 $ [0,5 .. ]
-- -> [0,5,10]
cycle [3,6 .. ] !! 4
-- -> 15
iterate (1+) 2 !! 3
-- -> 5
Further, code can contain:
if exp then exp else exp
case exp of {alternatives}
do {statements}
- Only if-then-else has expressions.
- case
alternatives
are maps that use->
instead of=
. statements
use<-
, if at all, and can use=
only in an optionalwhere
.
do
is syntactic sugar for a Monad binding operator (>>=
), which forwards output of the function in the previous line to the input of the function in the next line, to allow imperative style fragments. It is not imperative, though, but function composition. Function composition is Haskell's way of a sequence, intermitted with let
or where
for cases in which not the full output is needed as input. Monad is detailed further down.
Class
class
contains function types and possibly default implementations. Class is short of type class, in the sense that more types are instances of a class.
An instance
provides implementations of the functions of a class for a specific data
type. Instances for one class can be scattered across many modules. import xyz()
imports only the instances.
class A1 a where f:: a -> a
class A2 a where g:: a -> a
data D = D Int
data E = E Int
instance A1 D where f (D n) = D (n+1)
instance A2 E where g (E n) = E (n+2)
:{
ff:: A1 a => a -> a
= u
ff u :}
= let d = D 3 in ff d
dd = let e = E 3 in ff e -- error: No instance of (A1 E) dd
If we make E
an instance of A1
, there is no error:
instance A1 E where f n = n
= let e = E 3 in ff e dd
When a module imports a class, its functions become public.
The function is constrained to the class, in which the function was declared.
Prelude> :info (<*>)
type Applicative :: (* -> *) -> Constraint
class Functor f => Applicative f where
...
(<*>) :: f (a -> b) -> f a -> f b
...
-- Defined in ‘GHC.Base’
infixl 4 <*>
Prelude> :info (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
-- Defined in ‘Data.Functor’
infixl 4 <$>
Starting from ghc/libraries/base/Prelude.hs
one can follow included modules. ghc/libraries/base/GHC/Base.hs
declares:
Semigroup, Monoid, Functor, Applicative, Monad, Alternative, MonadPlus
Here some example usages for Prelude classes:
:info Semigroup
1,2] <> [4,5]
[-- -> [1,2,4,5]
:info Monoid
1,2,3] <> []
[-- -> [1,2,3]
:info Functor
+10) <$> [1,2,3] -- or fmap
(-- -> [11,12,13]
:info Applicative
+) <$> [1,2] <*> [3,4] -- same infixl 4
(-- -> [4,5,5,6]
1 <$ [1,2,3]
-- -> [1,1,1]
+) (Just 1) (Just 2)
liftA2 (-- -> Just 3
+) <$> Just 1 <*> Just 2
(-- -> Just 3
Since functions are passed around in Haskell, type classes have functions that accept functions as arguments and apply them to the data. This result in classes (Functor, Applicative, Monad, ...) that you don't see among the interfaces of data oriented languages.
The full usage intention behind a class cannot be read from the function signature. Additional laws (see Typeclassopedia) can be the basis for further thinking to grasp the intended generality.
(<>) :: Semigroup a => a -> a -> a
is binary. That we stay within the same type (a
) (closedness) makes sure that the associative law stays. The associativity law (a <> b) <> c == a <> (b <> c)
allows to infer
- that the time sequence does not matter (one could calculate chunks of a chain in any order or in parallel) and
- that consequently the space sequence fully identifies the result
A law like this is quite general, but still reduces all possible cases quite a bit, and thus has information.
Monoid
adds the empty
. A neutral element allows usage of the concept where there is nothing fitting to it. The neutral 0 allowed the transition from roman numerals, where the quantity grouping had to be named, to position coded numbers, where you place a 0 in a position, if the value of the position is not there.
(<$>) :: Functor f => (a -> b) -> f a -> f b
- injects a function
a -> b
(first argument) - into a constructed/derived type (second argument)
<$>
is also called fmap
(functor map). A functor maps one category into another. This is also called lifting (liftA
, liftA2
, ...).
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
assumes a lifted function, which is then applied in the new category.
<$>
lifts the arguments and applies the function.pure
just lifts, without looking at the arguments.<*>
only applies.
So Applicative
splits a Functor
's fmap
into two parts.
import GHC.Base
*) <$> [2, 3] ) <*> [6,7]
( (*) [2,3] [6,7]
liftA2 (-- all -> [12,14,18,21]
fmap (*10) [6,7]
*10) [6,7]
liftA (pure (*10) <*> [6,7]
-- all -> [60, 70]
pure (*10) *> [6,7]
6,7] <* pure (*10)
[-- all -> [6, 7]
import Control.Applicative
:{
digit :: Int -> String -> Maybe Int
= Nothing
digit _ [] :_) | i > 9 || i < 0 = Nothing
digit i (c| otherwise = if [c] == show i then Just i else Nothing
:}
0 "01"
digit -- -> Just 3
1 "01"
digit -- -> Nothing
= digit 0 s <|> digit 1 s
binChar s "01"
binChar -- -> Just 0
"10"
binChar -- -> Just 1
Alternative
adds the idea of Monoid
to the Functor-Applicative
line, with <|>
instead of <>
(Typeclassopedia). It also implements some
and many
. They are only useful for types where the constructor calls a function that processes input: a parser.
some
stops when the firstempty
is constructed, andmany
continues recursive application of the constructor beyondempty
Monad
A monad constructs and forwards context.
In a functional programming language context is built via the parameters of contained functions.
import Control.Monad
:info Monad
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
In:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
m
is a constructor of a type that is instance of the Monad classm a
is NOT a constructor application but a pattern matching to extractm
anda
a -> m b
is a pattern against a function with targetm b
. Let's call itk
.>>=
needs to map to whatk
maps to, i.e. applyk a
. The implementation fromMaybe
:(Just x) >>= k = k x
In a do
a <- exp [args]; nextexp
stands forexp >>= (\a -> nextexp)
exp [args]
constructs a value that would be pattern matched usingm a
>>=
composesa <- exp [args]
with the next expression>>
composesexp; nextexp
return :: Monad m => a -> m a
return
is basically the same as m
, but since m
can be any constructor it is good that we can refer to it generally with this one name.
->
is a Monad
->
constructs a type via lambda encapsulation (currying).
instance Monad ((->) r) where
>>= k = \ r -> k (f r) r f
f
is the application so far (a lambda)k
is the next->
k
is applied to what was before (f r
) and what comes after (r
)
IO is a Monad:
do {putStr "Hi"; putStrLn " there!"; }
putStr "Hi" >> putStrLn " there"
readLn >>= print
[]
is a Monad
You can use this to do SQL like queries.
= do {val <- vals; return (prop val);} -- @val <- vals@ needed
sel prop vals data Name = Name { firstName ::String , lastName :: String } deriving Show
= [ Name "Audre" "Lorde", Name "Leslie" "Silko", Name "Jo" "Silko"]
children
sel firstName children-- -> ["Audre","Leslie","Jo"]
import Control.Monad -- for guard
= do {val <- vals; guard (test val); return val; }
wh test vals ->'A'==(head s)) (sel firstName children)
wh (\sdata Family = Family { name ::String } deriving Show
= [ Family "Lorde", Family "Silko" ]
families = [ (d,e) | d<-d1, e<-d2, p1 d == p2 e]
jn d1 d2 p1 p2
jn families children name lastName.snd) (wh (((==) "Silko").name.fst) (jn families children name lastName))
sel (firstName= s (w j)
q s j w .snd)) (jn families children name lastName) (wh (((==) "Silko").name.fst)) q (sel (firstName
State
is a Monad
import Control.Monad.State
do { put 5; return 'X' }) 1
runState (-- -> ('X',5)
+1)) 1 evalState (gets (
Maybe
is a Monad
import Data.Maybe
Just 3, Nothing, Just 9]
catMaybes [-- -> [3,9]
:{
printLengthPrint :: Int -> Maybe Double
= \w -> Just (show w) -- :: Int -> Maybe String
printLengthPrint >>= \x -> Just (length x) -- :: String -> Maybe Int
>>= \y -> Just (2.0 ^^ y) -- :: Int -> Maybe Double
:}
32
printLengthPrint -- -> Just 4.0
:{
f :: Int -> Maybe String
= Just . show
f g :: String -> Maybe Int
= Just . length
g h :: Int -> Maybe Double
= Just . (2.0 ^^)
h :}
import Control.Monad
= h <=< g <=< f
plp1 32
plp1 = f >=> g >=> h
plp2 32 plp2
Monad transformer
A Monad transformer constructs a Monad from other monads.
The monad transformer library (mtl) is part of the ghc.
Extensions
The Haskell standard gets updated only every 10 years. Development in between can get activated via extensions.
{-# <EXTENSION>, ... #-}
-- or GHCi:
:set -X<EXTENSION>
Here some common ones from the GHC extension list:
- OverloadedStrings: When using Data.Text instead of String
- FlexibleInstances: nested types in head of instance
- FlexibleContexts: class context of kind
class (Cls1 a, Cls2 a) => ...
- AllowAmbiguousTypes: let call decide and not ambiguity checker
- ViewPatterns: include function result in pattern match
- PaternSynonym: e.g.
pattern NoBlending = #{const SDL_BLENDMODE_NONE} :: CInt
- RecordWildCards:
f (ARec{..}) = e1 ...
instead off (ARec e1 ...) = e1 ...
- NamedFieldPuns: when matching against less fields:
f (ARec{ex}) = ex
instead off (Arec{ex=ex}) = ex
- DeriveFunctor:
deriving Functor
derives functor instance - TypeApplications: specify type when called, e.g.
show (read @Int "5")
- BangPatterns:
f (!x,y)
strict (not lazy) inx
but not iny
- MultiParamTypeClasses implies FunctionalDependencies:
class C a b ...| a b -> c
- LambdaCase:
\case { p1 -> e1; ...; pN -> eN }
for\p -> case p of {...
- BlockArguments:
when (x>0) do ...
instead ofwhen (x>0) (do ...)
- MonadComprehensions:
[ x+y | x <- [1..10], y <- [1..x], then take 2 ]
(comprehension for all monads) - RebindableSyntax: bind to whatever in scope and not to
Prelude
- TransformListComp: in list comprehension
then f [by|using field]
(f=sortWith|group|..
) - GADTs: constructors as parametrized constrained type functions
- TypeFamilies:
type
function withinclass
to letinstance
compute the type - TemplateHaskell: compile-time macros to generate Haskell code
24 GHC Extensions gives alternative examples to some extensions.
Haskell has no Sequence, Loop, OOP
Object-oriented programming (OOP) gives different data a common interface to be passed to functions. In Haskell, interfaces are called (type) classes and they give different data a common way to inject (e.g. liftM
) and compose functions on it (e.g. >>=
).
Haskell is about composing functions:
- sequences are replaced with function compositions
- loops are replaced with recursive function compositions
- if-then-else and case could be functions
Compared to OOP in Haskell:
type
class
is what interface is in OOP.class
can also have function implementations (default implementations).data
ornewtype
is the object type called class in OOP.- They can have more constructors and recursive constructors
- They can have fields that are
- other data types (corresponds to OOP inheritance)
- functions (runtime polymorphism in OOP)
An
instance
constrains adata
type to aclass
.
Note the shift of meaning of class
and instance
respect to OOP:
- OOP: interface - class - constructor to memory
- Haskell: class - instance, data - constructor to memory
Pattern matching is a way to associate code to data
without an instance
declaration.
There is the Lens library to allows access fields in OOP style (needs an install: cabal install --lib microlens-platform
).
Generic programming in called parametrized polymorphism in Haskell, as it is done via parametrizing types and classes
- parametrized
data
(ADT), extended through GADTs - parametrized
class
, extended through MultiParamTypeClasses
GHC.Generics
allows to derive instance methods for user classes based on a generic implementation, similar to .. deriving (Eq,Ord,Show)
for built-in classes.
Extensions:
- DeriveGeneric:
deriving Generic
generatesinstance Generic <usertype> ...
- DefaultSignatures: change signature of
class
default implementations - DeriveAnyClass: allows
deriving (<UserClass>)
Then there is template meta-programming with TemplateHaskell, to create Haskell code on the fly, like a C macro.
Epilogue
To program functionally, in data and code, express yourself with
- pattern matching functions
- recursion
- currying
- pointless
It is a path with problems, too, and their solutions, an evolutionary branch of programming.
No comments:
Post a Comment