-- actually in 'base'
class Semigroup m where
(<>) :: m -> m -> m
Associativity 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 :: m
Identity laws for Monoid:
2. x <> mempty ≡ x
3. mempty <> x ≡ x
1. (++)
2. (+)
3. (*)
class Semigroup a where
(<>) :: a -> a -> a
sconcat :: NonEmpty a -> a
stimes :: Integral b => b -> a -> a
instance 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 value
Use 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] -> a
instance 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 1
newtype 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 = Nothing
ghci> 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 mempty
instance Semigroup (Endo a) where
Endo f <> Endo g = Endo (f . g)
instance Monoid (Endo a) where
mempty = Endo id
foldr :: (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]
60
foldr (\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 -> b
Some 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 xs
instance Foldable Maybe where
foldr :: (a -> b -> b) -> b -> Maybe a -> b
foldr _ z Nothing = z
foldr f z (Just x) = f x z
Reasons to drop Haskell
ghci> length (4, [1,2,3])
???
ghci> foldr (+) 1 (Just 3)
4
ghci> foldr (+) 0 Nothing
0
ghci> length (4, [1,2,3])
1
foldr (+) 0 (1 : 2 : 3 : []) ≡
1 + 2 + 3 + 0
Think of folds as replacing constructors, i.e. ':', '[]', with functions/values.
ghci> import Debug.SimpleReflect
ghci> sum [1..5] :: Expr
0 + 1 + 2 + 3 + 4 + 5
ghci> foldr f x [a,b,c]
f a ( f b ( f c x ) )
ghci> fmap (+3) (Just 2)
Just 5
ghci> fmap (+3) Nothing
Nothing
class Functor f where -- f :: Type -> Type
fmap :: (a -> b) -> f a -> f b
1. Identity
fmap id ≡ id
2. Composition
fmap (f . g) ≡ fmap f . fmap g
These 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 1
instance Functor [] where
fmap :: (a -> b) -> [a] -> [b]
fmap = map
ghci> 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 inaccurate
instance Functor ((->) r) where
fmap :: (a -> b) -> (r -> a) -> r -> b
fmap = (.) -- fmap f = \g -> f . g,
-- this is known as POST-COMPOSITION in category theory
ghci> let foo = fmap (+3) (+2)
ghci> foo 10
15
ghci> :kind (->) Int
(->) Int :: Type -> Type
fmap (*) (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.1
Having 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 context
liftA2 :: (a -> b -> c) -> f a -> f b -> f c -- meaning: we pass a BINARY function
A 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
Nothing
ghci> 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 -> a
ghci> (+) <$> (+3) <*> (*100) $ 5
508
ghci> (\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 <*> u
ghci> (*) <$> Just 5 <*> Just 3
Just 15
ghci> liftA2 (*) (Just 5) (Just 3)
Just 15
?ghci> :t liftA3
Apply 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 d
ghci> 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'
False
data 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 a
instance Alternative Maybe where
empty :: Maybe a
empty = Nothing
(<|>) :: Maybe a -> Maybe a -> Maybe a
Nothing <|> r = r
l <|> _ = l
ghci> Nothing <|> Just 3 <|> empty <|> Just 5
Just 3
instance 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 x
instance Traversable [] where
traverse :: Applicative f => (a -> f b) -> [a] -> f [b]
traverse f = foldr consF (pure [])
where
consF x ys = (:) <$> f x <*> ys
instance 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 String
class Hashable a where
hash :: a -> Hash
hashPair :: 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 constructors
class Hashable a where
hash :: a -> Hash a
hashPair :: 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 n
Crypto
newtype Signature a = Signature ByteString
sign :: Binary t => SecretKey -> t -> Signature t
data 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 t
data 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 -> a
This definition...
...just contains an implicit forall!
id :: forall a . a -> a
These 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 -> a
ghci> :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 -> Int
ghci> read "3" :: Int -- so, here we basically just pass Int type
3
ghci> read "3.0" :: Double
3.0
Now we can pass types as arguments to functions!
newtype First a = First { getFirst :: a } -- first value
newtype Last a = Last { getLast :: a } -- last value
How 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 = Nothing
Maybe 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 Just
instance Monoid b => Monoid (a -> b) where
mempty _ = mempty
mappend f g x = f x `mappend` g x
Told 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` _ = GT
module 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 offset
For 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 b
ghci> 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:10
ghci> 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