ITMO CTD Haskell
Lecture slides on Functional programming course at the ITMO university CT department. You can find course description here: https://github.com/jagajaga/FP-Course-ITMO
-- actually in 'base'
class Semigroup m where
(<>) :: m -> m -> mAssociativity law for Semigroup:
1. (x <> y) <> z ≡ x <> (y <> z)1. (++)
2. max/min
3. (+)
4. (*)-- also in 'base' but...
class Semigroup m => Monoid m where
mempty :: mIdentity laws for Monoid:
2. x <> mempty ≡ x
3. mempty <> x ≡ x1. (++)
2. (+)
3. (*)class Semigroup a where
(<>) :: a -> a -> a
sconcat :: NonEmpty a -> a
stimes :: Integral b => b -> a -> ainstance Semigroup [a] where
(<>) = (++)ghci> [1..5] <> [2,4..10]
[1,2,3,4,5,2,4,6,8,10]>>> 3 * list(range(1,6))
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]ghci> concat (replicate 3 [1..5])
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]ghci> 3 `stimes` [1..5]
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]instance Semigroup Int where
-- Which one to choose?
1. (<>) = (+)
2. (<>) = (*)We can combine numbers as well. But how?
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }instance Num a => Semigroup (Sum a) where
Sum x <> Sum y = Sum (x + y)
instance Num a => Semigroup (Product a) where
Product x <> Product y = Product (x * y)The Haskell way: emulate multiple instances for one type with multiple newtypes (wrappers)
ghci> 3 <> 5 :: Sum Int
Sum { getSum = 8 }
ghci> 3 <> 5 :: Product Int
Product { getProduct = 15 }newtype Max a = Max { getMax :: a } -- max
newtype Min a = Min { getMin :: a } -- min
newtype Any = Any { getAny :: Bool } -- ||
newtype All = All { getAll :: Bool } -- &&
newtype First a = First { getFirst :: a } -- first value
newtype Last a = Last { getLast :: a } -- last valueUse the same approach for different situations
ghci> Max 3 <> Max 10 <> Max 2
Max { getMax = 10 }
ghci> Min 3 <> Min 10 <> Min 2
Min { getMin = 2 }
ghci> Any True <> Any False <> Any True
Any { getAny = True }
ghci> All True <> All False <> All True
All { getAll = False }
ghci> First Nothing <> First (Just 10) <> First (Just 1)
First { getFirst = Nothing }
ghci> Last [] <> Last [5, 3, 6] <> Last [11, 9 .. -5]
Last { getLast = [11, 9 .. -5] }class Semigroup a => Monoid a where
mempty :: a
mappend :: a -> a -> a -- (<>) by default
mconcat :: [a] -> ainstance Monoid [a] where
mempty = []And all the rest is almost the same.
instance (Monoid a, Monoid b) => Monoid (a, b) where
mempty = ( mempty, mempty)
(a1, b1) `mappend` (a2, b2) = (a1 `mappend` a2, b1 `mappend` b2)instance Num a => Monoid (Sum a) where
mempty = Sum 0
instance Num a => Monoid (Product a) where
mempty = Product 1newtype Any = Any { getAny :: Bool } -- ||, mempty = False
newtype All = All { getAll :: Bool } -- &&, mempty = True
newtype First a = First { getFirst :: Maybe a } -- first Just, mempty = Nothing
newtype Last a = Last { getLast :: Maybe a } -- last Just, mempty = Nothingghci> mempty <> First (Just 10) <> First (Just 1)
First { getFirst = Just 10 }
ghci> Last (Just 31) <> Last (Just 22) <> mempty
Last { getLast = Just 22 }newtype Dual a = Dual { getDual :: a } -- passes the arguments of (<>)
-- in REVERSE order
newtype Endo a = Endo { appEndo :: a -> a } -- the monoid of endomorphisms on 'a' instance Semigroup a => Semigroup (Dual a) where
Dual x <> Dual y = Dual (y <> x)
instance Monoid a => Monoid (Dual a) where
mempty = Dual memptyinstance Semigroup (Endo a) where
Endo f <> Endo g = Endo (f . g)
instance Monoid (Endo a) where
mempty = Endo idfoldr :: (a -> b -> b) -> b -> [a] -> b
foldl :: (b -> a -> b) -> b -> [a] -> b Eliminating (recursing on) a list
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b Generalization for arbitrary containers
ghci> foldr (+) 0 [2, 1, 10]
13
ghci> foldr (*) 3 [2, 1, 10]
60foldr (\s rest -> rest + length s) 0 ["aaa", "bbb", "s"]
foldr (\rest s -> rest + length s) 0 ["aaa", "bbb", "s"]Which one is correct?
-- | Simplified version of Foldable
class Foldable t where
{-# MINIMAL foldMap | foldr #-}
fold :: Monoid m => t m -> m
foldMap :: Monoid m => (a -> m) -> t a -> m
foldr :: (a -> b -> b) -> b -> t a -> bSome basic instances
instance Foldable [] where
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr _ z [] = z
foldr f z (x:xs) = x `f` foldr f z xsinstance Foldable Maybe where
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr _ z Nothing = z
foldr f z (Just x) = f x zReasons to drop Haskell
ghci> length (4, [1,2,3])
???ghci> foldr (+) 1 (Just 3)
4
ghci> foldr (+) 0 Nothing
0ghci> length (4, [1,2,3])
1foldr (+) 0 (1 : 2 : 3 : []) ≡
1 + 2 + 3 + 0Think of folds as replacing constructors, i.e. ':', '[]', with functions/values.
ghci> import Debug.SimpleReflect
ghci> sum [1..5] :: Expr
0 + 1 + 2 + 3 + 4 + 5ghci> foldr f x [a,b,c]
f a ( f b ( f c x ) )ghci> fmap (+3) (Just 2)
Just 5
ghci> fmap (+3) Nothing
Nothingclass Functor f where -- f :: Type -> Type
fmap :: (a -> b) -> f a -> f b1. Identity
fmap id ≡ id
2. Composition
fmap (f . g) ≡ fmap f . fmap gThese two laws state that fmap is a homomorphism that preserves composition and identities.
instance Functor Maybe where
fmap :: (a -> b) -> Maybe a -> Maybe b
fmap f (Just x) =
fmap f Nothing =
instance Functor Maybe where
fmap :: (a -> b) -> Maybe a -> Maybe b
fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothing
post = Post.find_by_id(1)
if post
return post.title
else
return nil
end
findPost :: PostId -> Maybe Post
getPostTitle :: Post -> Title
firstPostTitle :: Maybe Title
firstPostTitle = fmap getPostTitle (findPost 1)infixl 4 <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap
firstPostTitle = getPostTitle <$> findPost 1instance Functor [] where
fmap :: (a -> b) -> [a] -> [b]
fmap = mapghci> fmap (*2) [1..3]
[2,4,6]
ghci> map (*2) [1..3]
[2,4,6]
ghci> fmap (*2) [] -- ?instance Functor ((->) r) where
fmap :: (a -> b) -> (r -> a) -> r -> b
fmap f g =ghci> :kind (->)
(->) :: Type -> Type -> Type -- this kind signature is enough for us,
-- albeit it's slightly inaccurateinstance Functor ((->) r) where
fmap :: (a -> b) -> (r -> a) -> r -> b
fmap = (.) -- fmap f = \g -> f . g,
-- this is known as POST-COMPOSITION in category theoryghci> let foo = fmap (+3) (+2)
ghci> foo 10
15
ghci> :kind (->) Int
(->) Int :: Type -> Typefmap (*) (Just 3) == Just (3 *)
ghci> let a = fmap (*) [1,2,3,4]
ghci> :t a
a :: [Integer -> Integer]
ghci> fmap (\f -> f 9) a
[9,18,27,36]class Functor f => Applicative f where -- f :: Type -> Type
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
ghci> :t fmap (++) (Just "hey")
fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])
ghci> :t fmap compare (Just 'a')
fmap compare (Just 'a') :: Maybe (Char -> Ordering) liftA2 :: (a -> b -> c) -> f a -> f b -> f c -- since GHC 8.2.1Having pure, (<*>), and liftA2 is enough to implement liftAN for fixed values of N!
pure :: a -> f a -- meaning: we pass a NULLARY function
-- i.e., we pass a constant and wrap it in the contextliftA2 :: (a -> b -> c) -> f a -> f b -> f c -- meaning: we pass a BINARY functionA type f is a Functor if and only if
But what if we wanted to pass N-nary functions of arbitrary arities??
instance Applicative Maybe where
pure :: a -> Maybe a
pure =
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
(<*>) =instance Applicative Maybe where
pure :: a -> Maybe a
pure = Just
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Nothing <*> _ = Nothing
Just f <*> something = fmap f something?ghci> pure (++"what") <*> pure "lol"ghci> Just (+3) <*> Just 9
Just 12
ghci> pure (+3) <*> Just 10
Just 13
ghci> Just (++"hahah") <*> Nothing
Nothingghci> pure (++"what") <*> pure "lol"
Just "lolwhat"ghci> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]instance Applicative [] where
pure :: a -> [a]
pure x =
(<*>) :: [a -> b] -> [a] -> [b]
fs <*> xs =instance Applicative [] where
pure :: a -> [a]
pure x = [x]
(<*>) :: [a -> b] -> [a] -> [b]
fs <*> xs = [f x | f <- fs, x <- xs]ghci> [ x * y | x <- [2, 5, 10],
y <- [8, 10, 11]]
[16, 20, 22, 40, 50, 55, 80, 100, 110]ghci> (*) <$> [2, 5, 10] <*> [8, 10, 11]
[16, 20, 22, 40, 50, 55, 80, 100, 110]ghci> (pure 3) "blah"
3
instance Applicative ((->) r) where
pure :: a -> r -> a -- the K combinator!
pure x =
(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b -- the S combinator!
f <*> g =
instance Applicative ((->) r) where
pure :: a -> r -> a -- the K combinator!
pure x = \_ -> x
(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b -- the S combinator!
f <*> g = \x -> f x (g x)ghci> :t (+) <$> (+3) <*> (*100)
(+) <$> (+3) <*> (*100) :: (Num a) => a -> aghci> (+) <$> (+3) <*> (*100) $ 5
508ghci> (\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5
[8.0,10.0,2.5]1. Identity
pure id <*> v ≡ v
2. Composition
pure (.) <*> u <*> v <*> w ≡ u <*> (v <*> w)
3. Homomorphism
pure f <*> pure x ≡ pure (f x)
4. Interchange
u <*> pure y ≡ pure ($ y) <*> u
5. Compatibility w/ Functors
fmap f u ≡ pure f <*> ughci> (*) <$> Just 5 <*> Just 3
Just 15
ghci> liftA2 (*) (Just 5) (Just 3)
Just 15
?ghci> :t liftA3Apply a function to several arguments?
Pfft, 2ez4konstantine
ghci> (*) <$> Just 5 <*> Just 3
Just 15
ghci> liftA2 (*) (Just 5) (Just 3)
Just 15
ghci> :t liftA3
liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f dghci> import Data.Char (isUpper, isDigit)
ghci> import Control.Applicative (liftA2)
ghci> let isUpperOrDigit = liftA2 (||) isUpper isDigit
ghci> :t isUpperOrDigit
isUpperOrDigit :: Char -> Bool
ghci> isUpperOrDigit 'A'
True
ghci> isUpperOrDigit '3'
True
ghci> isUpperOrDigit 'a'
Falsedata User = User
{ userFirstName :: String
, userLastName :: String
, userEmail :: String
}type Profile = [(String, String)]
profileExample =
[ ("first_name", "Pat" )
, ("last_name" , "Brisbin" )
, ("email" , "me@pbrisbin.com")
]lookup "first_name" p :: Maybe String
buildUser :: Profile -> Maybe User
buildUser p = User
<$> lookup "first_name" p
<*> lookup "last_name" p
<*> lookup "email" p-- using liftAN common pattern
buildUser :: Profile -> Maybe User
buildUser p = liftA3 User
(lookup "first_name" p)
(lookup "last_name" p)
(lookup "email" p)-- point-free version
buildUser :: Profile -> Maybe User
buildUser = liftA3 (liftA3 User)
(lookup "first_name")
(lookup "last_name")
(lookup "email")class Applicative f => Alternative f where
empty :: f a
(<|>) :: f a -> f a -> f ainstance Alternative Maybe where
empty :: Maybe a
empty = Nothing
(<|>) :: Maybe a -> Maybe a -> Maybe a
Nothing <|> r = r
l <|> _ = lghci> Nothing <|> Just 3 <|> empty <|> Just 5
Just 3instance Alternative [] where
empty :: [a]
empty = []
(<|>) :: [a] -> [a] -> [a]
(<|>) = (++)ghci> [] <|> [1,2,3] <|> [4]
[1,2,3,4]Doing the same as Foldable but preserving the shape.
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
sequenceA :: Applicative f => t (f a) -> f (t a)ghci> let half x = if even x then Just (x `div` 2) else Nothing
ghci> traverse half [2, 4 .. 10]
Just [1, 2, 3, 4, 5]
ghci> traverse half [1 .. 10]
Nothing
instance Traversable Maybe where
traverse :: Applicative f => (a -> f b) -> Maybe a -> f (Maybe b)
traverse _ Nothing =
traverse f (Just x) =instance Traversable Maybe where
traverse :: Applicative f => (a -> f b) -> Maybe a -> f (Maybe b)
traverse _ Nothing = pure Nothing
traverse f (Just x) = Just <$> f xinstance Traversable [] where
traverse :: Applicative f => (a -> f b) -> [a] -> f [b]
traverse f = foldr consF (pure [])
where
consF x ys = (:) <$> f x <*> ysinstance Traversable [] where
traverse :: Applicative f => (a -> f b) -> [a] -> f [b]
traverse f l =
There exist Bifunctor, Biapplicative, Bifoldable and Bitraversable.
If possible, GHC can derive some instances automatically.
{-# LANGUAGE DeriveFunctor #-} -- generates `fmap`
{-# LANGUAGE DeriveFoldable #-} -- generates `foldr`, `foldMap`, and `null`
{-# LANGUAGE DeriveTraversable #-} -- generates `traverse`
data Tree a = Leaf | Node a (Tree a) (Tree a)
deriving (Functor, Foldable, Traversable)newtype Hash = MkHash Stringclass Hashable a where
hash :: a -> HashhashPair :: Int -> Hash
hashPair n = hash (n, n)hashPair :: Int -> Hash
hashPair n = hash n -- oops, this is a valid definition!But types can't help you much if you don't use their true power.
newtype Hash a = MkHash String -- `a` is a phantom type,
-- not present in any of the constructorsclass Hashable a where
hash :: a -> Hash ahashPair :: Int -> Hash (Int, Int)
hashPair n = hash (n, n)hashPair :: Int -> Hash (Int, Int)
hashPair n = hash n -- no longer a valid definition!Hash.hs:7:14: error:
• Couldn't match type ‘Int’ with ‘(Int, Int)’
Expected type: Hash (Int, Int)
Actual type: Hash Int
• In the expression: hash n
In an equation for ‘hashPair’: hashPair n = hash nCrypto
newtype Signature a = Signature ByteString
sign :: Binary t => SecretKey -> t -> Signature tdata Abs -- absolute path
data Rel -- relative path
data Dir -- directory
data File -- file
newtype Path b t = Path FilePath
-- appending paths
(</>) :: Path b Dir -> Path Rel t -> Path b tdata Second
data Microsecond -- rough approximation
newtype Time unit = Time (Ratio Natural)-- v0.0.2: also compiles
prepend2 :: Int -> [Int] -> [Int]
prepend2 x xs = pair ++ xs
where pair = [x, x]-- v0.2.1: doesn't compile!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair :: [a]
pair = [x, x] -- v0.0.1: this compiles
prepend2 :: Int -> [Int] -> [Int]
prepend2 x xs = pairFun x ++ xs
where pairFun y = [y, y]-- v0.1.0: compiles or doesn't?
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair = [x, x]-- v0.2.0: everything works!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pairFun x ++ xs
where pairFun :: a -> [a]
pairFun y = [y, y] -- v0.2.1: doesn't compile!
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where pair :: [a]
pair = [x, x] Main.hs:7:17: error:
• Couldn't match expected type ‘a1’ with actual type ‘a’
‘a1’ is a rigid type variable bound by
the type signature for:
pair :: forall a1. [a1]
at Main.hs:6:9-19
‘a’ is a rigid type variable bound by
the type signature for:
prepend2 :: forall a. a -> [a] -> [a]
at Main.hs:4:1-27
• In the expression: x
In the expression: [x, x]
In an equation for ‘pair’: pair = [x, x]
• Relevant bindings include
pair :: [a1] (bound at Main.hs:7:9)
xs :: [a] (bound at Main.hs:5:12)
x :: a (bound at Main.hs:5:10)
prepend2 :: a -> [a] -> [a] (bound at Main.hs:5:1)
|
7 | pair = [x, x]
|
(This is compiled with GHC 9.4.8)prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: [a]
pair = [x, x] id :: a -> aThis definition...
...just contains an implicit forall!
id :: forall a . a -> aThese foralls bind completely different type variables.
prepend2 :: forall a . a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: forall a . [a]
pair = [x, x] Same for the definitions inside the where block!
-- it's basically the same reason as to why THIS doesn't compile
prepend2 :: forall a b . b -> [a] -> [a]
prepend2 x xs = [x, x] ++ xs {-# LANGUAGE ScopedTypeVariables #-}
-- v1.0.0: COMPILES! Huzzah!
prepend2 :: forall a . a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: [a] -- uses same type variable 'a'
pair = [x, x] The -XScopedTypeVariables language extension allows to use type variables from a top-level function signature inside this function's body (including the where block). Works only with the forall keyword!
{-# LANGUAGE ScopedTypeVariables #-}
-- v1.0.1: nooo what have you done?! -_-
prepend2 :: a -> [a] -> [a]
prepend2 x xs = pair ++ xs
where
pair :: [a] -- throws the SAME error, because the top-level forall is ABSENT
pair = [x, x] ghci> read "3" :: Int
3
ghci> read "3.0" :: Double
3.0-- in some artifical syntax...
read :: (a :: Type) -> (_ :: String) -> (x :: a)read :: forall a . Read a => String -> aghci> :t read
read :: Read a => String -> a
ghci> read @Int "3"
3
ghci> read @Double "3.0"
3.0
ghci> :t read @Int
read @Int :: String -> Intghci> read "3" :: Int -- so, here we basically just pass Int type
3
ghci> read "3.0" :: Double
3.0Now we can pass types as arguments to functions!
newtype First a = First { getFirst :: a } -- first value
newtype Last a = Last { getLast :: a } -- last valueHow to write mempty for First and Last?
instance Semigroup a => Semigroup (Maybe a) where
Nothing <> b = b
a <> Nothing = a
Just a <> Just b = Just (a <> b)
instance Semigroup a => Monoid (Maybe a) where
mempty = NothingMaybe creates a FREE Monoid for a given Semigroup :0
Once again, Last and First from the Data.Monoid module differ from the ones in Data.Semigroup:
newtype First a = First { getFirst :: Maybe a } -- first Just
newtype Last a = Last { getLast :: Maybe a } -- last Justinstance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g xTold ya, everything is a Monoid :]
ghci> (flip take [3, 2, 1] <> replicate 2) 1
[3, 1, 1]But what does it mean? Something like this:
data Ordering = LT | EQ | GT
instance Monoid Ordering where
mempty = EQ
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GTmodule Data.Ord (comparing, ...) where
comparing :: Ord a => (b -> a) -> b -> b -> Ordering
comparing p x y = compare (p x) (p y)Why do we need such an instance?
data ErrorPosition = ErrorPosition
{ path :: String
, line :: Int
, offset :: Int
} deriving (Eq)
-- this implementation uses the Monoid instances of (->) and Ordering
instance Ord ErrorPosition where
compare = comparing path <> comparing line <> comparing offsetFor example, to lexicographically sort values of a datatype
Composability is what FP for
1. Combination of plugins is a plugin
2. Combination of configurations is configuration of the same type
3. Combination of event sources is an event source
4. Combination of clusters is a cluster
5. Combination of parsers is a parser
6. Combination of streams is a stream
7. Combination of functions is a function
and so on...
data Options = Options
{ oRetryCount :: Int
, oHost :: String
, oCharacterCode :: Maybe Char
} deriving (Show, Eq)Goal: we want to have options. We want to create multiple versions of records and combine them (defaults + file config + CLI)
data PartialOptions = PartialOptions
{ poRetryCount :: Last Int
, poHost :: Last String
, poCharacterCode :: Last (Maybe Char)
} deriving (Show, Eq)instance Monoid PartialOptions where
mempty = PartialOptions mempty mempty mempty
mappend x y = PartialOptions
{ poRetryCount = poRetryCount x <> poRetryCount y
, poHost = poHost x <> poHost y
, poCharacterCode = poCharacterCode x <> poCharacterCode y
}ghci> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
ghci> :t fmap fmap
fmap fmap
:: (Functor f, Functor f1) => f1 (a -> b) -> f1 (f a -> f b)
ghci> :t fmap fmap fmap
fmap fmap fmap
:: (Functor f, Functor f1) => (a -> b) -> f1 (f a) -> f1 (f b)
ghci> :t fmap fmap fmap fmap
fmap fmap fmap fmap
:: (Functor f, Functor f1, Functor f2) =>
f2 (f1 (a -> b)) -> f2 (f1 (f a -> f b))
ghci> :t fmap fmap fmap fmap fmap
fmap fmap fmap fmap fmap
:: Functor f => (a1 -> b) -> (a -> a1) -> f a -> f bghci> let fmap5 = fmap fmap fmap fmap fmap
ghci> fmap5 (+1) (*2) [1..5]
[3, 5, 7, 9, 11]-- do you see problems with this code?
class Size a where
size :: Int • Could not deduce (Size a0)
from the context: Size a
bound by the type signature for:
size :: forall {k} (a :: k). Size a => Int
at Main.hs:7:3-13
The type variable ‘a0’ is ambiguous{-# LANGUAGE AllowAmbiguousTypes #-}
class Size a where size :: Int
instance Size Int where size = 8
instance Size Double where size = 16
ghci> size -- this all looks great, but how do I MEANINGFULLY CALL the function?!
• Ambiguous type variable ‘a0’ arising from a use of ‘size’
prevents the constraint ‘(Size a0)’ from being solved.
Probable fix: use a type annotation to specify what ‘a0’ should be.
Potentially matching instances:
instance Size Double -- Defined at Main.hs:14:10
instance Size Int -- Defined at Main.hs:11:10ghci> size @Double -- use -XTypeApplications
16-- simple plain Haskell
class Size a where
size :: a -> Int-- this one's better
class Size a where
size :: Proxy a -> Int
-- data Proxy t = Proxy-- v0.0.0: this doesn't work IN GENERAL, only works for 'Integer's
incShow :: String -> String
incShow = show . (+1) . read
ghci> incShow @Int "3"
"4"
ghci> incShow @Double "3.2"
"4.2"
ghci> incShow @Rational "3 % 5"
"8 % 5"
-- v0.0.1: why doesn't THIS work?
incShow :: (Show a, Num a, Read a) => String -> String
incShow = show . (+1) . read
{-# LANGUAGE AllowAmbiguousTypes #-}
-- v0.0.2: why doesn't it STILL work?!
incShow :: (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
-- v1.0.0: free, at last
incShow :: forall a . (Read a, Show a, Num a) => String -> String
incShow = show . (+1) . read @a
By ITMO CTD Haskell
Lecture about Functors, Applicatives, Traversable with its instances, automatic deriving extensions and some type hierarchy proposals.
Lecture slides on Functional programming course at the ITMO university CT department. You can find course description here: https://github.com/jagajaga/FP-Course-ITMO