2 things we can do with a value:
- generate/construct
- match/consume
Data is immutable in Haskell, so values carry with them the information about how they were created. We can use that information when we consume or deconstruct the value.
Some example data types
data GuessWhat =
Chickenbutt deriving (Eq, Show)
data Id a =
MkId a deriving (Eq, Show)
data Product a b =
Product a b deriving (Eq, Show)
data RecordProduct a b =
RecordProduct { pfirst :: a
, psecond :: b }
deriving (Eq, Show)
data Sum a b =
First a
| Second b
deriving (Eq, Show)
In ordinary Haskell code, it’s unlikely you’d need or want nestable sums and products unless you were doing something fairly advanced,
The actual types Sum and Product themselves aren’t used very often in standard Haskell code, but it can be useful to develop an intuition about this structure to sum and product types.
trivialValue :: GuessWhat
trivialValue = Chickenbutt
idInt :: Id Integer
idInt = MkId 10
I don't understand this example and the following passage.
idIdentity :: Id (a -> a)
idIdentity = MkId $ \x -> x
This is a little odd. The type Id takes an argument and the data constructor MkId takes an argument of the corresponding polymor- phic type. So, in order to have a value of type Id Integer, we need to apply a -> Id a to an Integer value. This binds the 𝑎 type variable to Integer and applies away the (->) in the type constructor, giving us Id Integer. We can also construct a MkId value that is an identity function by binding the 𝑎 to a polymorphic function in both the type and the term level.
data Twitter =
Twitter deriving (Eq, Show)
data AskFm =
AskFm deriving (Eq, Show)
socialNetwork :: Sum Twitter AskFm
socialNetwork = First Twitter
Order is important. We must pair First
and Second
with the appropriate data type
Prelude> Second Twitter :: Sum Twitter AskFm
-- ERROR
Using type synonyms instead of data types is dangerous. In this example we prevent the type checker from being able to help us catch errors. Don't do this.
type Twitter = String
type AskFm = String
twitter :: Sum Twitter AskFm
twitter = First "Twitter"
-- It has no way of knowing
-- we made a mistake because
-- both values are just Strings
askfm :: Sum Twitter AskFm
askfm = First "AskFm"
you can construct values of products that use record syntax in a manner identical to that of non-record products
myRecord :: RecordProduct Integer Float
myRecord = RecordProduct 42 0.00001
myRecord' = RecordProduct { pfirst = 42
, psecond = 0.00001 }
You can reorder the constructor fields when using record syntax
nineToFive :: Programmer
nineToFive = Programmer { os = Mac
, lang = Haskell }
feelingWizardly :: Programmer
feelingWizardly = Programmer { lang = Agda
, os = GnuPlusLinux }
A reminder that the field names act as functions
Prelude> os feelingWizardly
GnuPlusLinux
Write a function that generates all possible values of Programmer.
allOperatingSystems :: [OperatingSystem]
allOperatingSystems =
[ GnuPlusLinux
, OpenBSDPlusNevermindJustBSDStill
, Mac
, Windows
]
allLanguages :: [ProgrammingLanguage]
allLanguages = [Haskell, Agda, Idris, PureScript]
allProgrammers :: [Programmer]
allProgrammers = [Programmer o l | o <- allOperatingSystems, l <- allLanguages]
It is only a warning to construct a value using record syntax but not specify all the fields. Don't do this.
Prelude> let partialAf = Programmer {os = GnuPlusLinux}
Prelude> partialAf
--ERROR
If you need to specify some of field values use a normal, ordered data constructor.
Some shade is thrown at the Builder Pattern https://en.wikipedia.org/wiki/Builder_pattern
Percolate values through your programs, not bottoms.
We explained that catamorphism was about deconstructing lists. This idea is generally applicable to any datatype that has values.
newtype Name = Name String deriving Show
newtype Acres = Acres Int deriving Show
data FarmerType = DairyFarmer
| WheatFarmer
| SoybeanFarmer deriving Show
data Farmer =
Farmer Name Acres FarmerType deriving Show
isDairyFarmer :: Farmer -> Bool
isDairyFarmer (Farmer _ _ DairyFarmer) = True
isDairyFarmer _ = False
Record version
data FarmerRec =
FarmerRec { name :: Name
, acres :: Acres
, farmerType :: FarmerType } deriving Show
isDairyFarmerRec :: FarmerRec -> Bool
isDairyFarmerRec farmer = case farmerType farmer of
DairyFarmer -> True
_ -> False
More warnings to avoid introducing bottoms. Don't do this
data Automobile' = Null'
| Car' { make' :: String
, model' :: String
, year' :: Integer }
deriving (Eq, Show)
Instead, define record-style product types separate from the sum types that incorporate them.
data Car = Car { make :: String
, model :: String
, year :: Integer }
deriving (Eq, Show)
data Automobile = Null
| Automobile Car deriving (Eq, Show)
When calculating the number of inhabitants of a function, exponentiate the number of inhabitants of the types.
a -> b = b^a
a -> b -> c = (c^b)^a = c^(b*a)
data Quantum =
Yes
| No
| Both
deriving (Eq, Show)
quantSum :: Either Quantum Quantum
quantProd :: (Quantum, Quantum)
quantFunc :: Quantum -> Quantum
There should be 2^3 = 8.
convert :: Quantum -> Bool
convert = undefined
convert1 :: Quantum -> Bool
convert1 Yes = True
convert1 No = True
convert1 Both = True
convert2 :: Quantum -> Bool
convert2 Yes = False
convert2 No = False
convert2 Both = False
convert3 :: Quantum -> Bool
convert3 Yes = True
convert3 No = False
convert3 Both = False
convert4 :: Quantum -> Bool
convert4 Yes = False
convert4 No = True
convert4 Both = False
convert5 :: Quantum -> Bool
convert5 Yes = False
convert5 No = False
convert5 Both = True
convert6 :: Quantum -> Bool
convert6 Yes = True
convert6 No = True
convert6 Both = False
convert7 :: Quantum -> Bool
convert7 Yes = False
convert7 No = True
convert7 Both = True
convert8 :: Quantum -> Bool
convert8 Yes = True
convert8 No = False
convert8 Both = True
Determine how many unique inhabitants each type has.
-
data Quad = One | Two | Three | Four deriving (Eq, Show)
-- how many different forms can this take? eQuad :: Either Quad Quad eQuad = ???
-
prodQuad :: (Quad, Quad) 4*4 = 16
-
funcQuad :: Quad -> Quad 4^4 = 256
-
prodTBool :: (Bool, Bool, Bool) (2*2)*2 = 8
-
gTwo :: Bool -> Bool -> Bool (2^2)^2 = 2^(2*2) = 2^4 = 16
-
Hint: 5 digit number fTwo :: Bool -> Quad -> Quad (4^4)^2 = 4^(4*2) = 4^8 = 65536
Higher-kind means that not all the types have been applied.
The kind of a product type has a * ->
for each component type and a *
for the final value.
data Silly a b c d = MkSilly a b c d deriving Show
Prelude> :kind Silly
Silly :: * -> * -> * -> * -> *
Prelude> :kind Silly Int String
Silly Int String :: * -> * -> *
Prelude> :kind Silly Int String Bool String
Silly Int String Bool String :: *
Fully applied types have a kind of *
Prelude> :kind (Int, String, Bool, String)
(Int, String, Bool, String) :: *
Real-world example of a higher kinded function.
I don't understand what is going on here.
data EsResultFound a =
EsResultFound { _version :: DocVersion
, _source :: a
} deriving (Eq, Show)
instance (FromJSON a) => FromJSON (EsResultFound a) where
parseJSON (Object v) = EsResultFound <$>
v .: "_version" <*>
v .: "_source"
parseJSON _ = empty
As you can hopefully see from this, by not fully applying the type — by leaving it higher-kinded — space is le for the type of the response to vary, for the “hole” to be filled in by the end user.
Lists are polymorphic because they can contain values of any type.
When we give an operator a non-alphanumeric name, it is infix by default. Any operator that starts with a colon (:) must be an infix type or data constructor.
data Product' a b =
a :&: b
deriving (Eq, Show)
Prelude> 1 :&: 2
1 :&: 2
Prelude> :t 1 :&: 2
1 :&: 2 :: (Num a, Num b) => Product' a b
Whether or not you choose to use infix data constructors, type constructors, or typeclass names is down to aesthetic preference.
List without infix operator :
data List a = Nil | Cons a (List a)
Has the same kinds as the native list
Prelude> :kind List
List :: * -> *
Prelude> :kind []
[] :: * -> *
Prelude> :kind List Int
List Int :: *
Prelude> :kind [Int]
[Int] :: *
Similar to a List. If only the right sides of all nodes are filled it is essentially the same as a list.
data BinaryTree a =
Leaf
| Node (BinaryTree a) a (BinaryTree a)
deriving (Eq, Ord, Show)
The first thing to be aware of is that we need Ord in order to have enough information about our values to know how to arrange them in our tree.
insert' :: Ord a => a -> BinaryTree a -> BinaryTree a
insert' b Leaf = Node Leaf b Leaf
insert' b (Node left a right)
| b == a = Node left a right
| b < a = Node (insert' b left) a right
| b > a = Node left a (insert' b right)
By convention we put lesser values in left-hand trees and greater values in right hand trees.
Data is immutable so we are always creating new trees when we add nodes.
The structure inherent in the def- inition of the type is all you need. Just write the recursive functions and get it done.
There was a lot of help text explaining how to solve this which makes me suspicious of 2-minute solution, even though is passes the simple test.
mapTree :: (a -> b) -> BinaryTree a -> BinaryTree b
mapTree _ Leaf = Leaf
mapTree f (Node left a right) =
Node (mapTree f left) (f a) (mapTree f right)
preorder :: BinaryTree a -> [a]
preorder Leaf = []
preorder (Node left a right) =
[a] ++ preorder left ++ preorder right
inorder :: BinaryTree a -> [a]
inorder Leaf = []
inorder (Node left a right) =
preorder left ++ [a] ++ preorder right
postorder :: BinaryTree a -> [a]
postorder Leaf = []
postorder (Node left a right) =
preorder left ++ preorder right ++ [a]
I started sketching a direct fold of the tree, stared at it in confusion for a few minutes, then realized it would be simple to just convert the tree to a list and then fold the list.
foldTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTree _ b Leaf = b
foldTree f b t = foldList f b (preorder t) where
foldList _ b [] = b
foldList f b (a:as) = (f a (foldList f b as))
- Write a function that capitalizes a word.
capitalizeWord :: String -> String
capitalizeWord "" = ""
capitalizeWord (c:cs) = toUpper c : cs
Example output. Prelude> capitalizeWord "Titter" "Titter" Prelude> capitalizeWord "titter" "Titter"
- Write a function that capitalizes sentences in a paragraph. Recognize when a new sentence has begun by checking for periods. Reuse the capitalizeWord function.
My solution depends on capitalizeWord
working with leading whitespace (i.e. capitalizeWord " foo" = " Foo")
capitalizeParagraph :: String -> String
capitalizeParagraph s = run "" s where
run acc "" = acc
run acc (c:cs)
| c == '.' = capitalizeWord acc ++ "." ++ run "" cs
| otherwise = run (acc ++ [c]) cs
I have a partial solution but I don't think it is what they are looking for since I did not define a DaPhone
data type.
keyMap :: [[(Digit, Presses)]]
keyMap = replicate 32 []
++ [[('0', 2)]]
++ replicate 2 []
++ [[('#', 1)]]
++ replicate 6 []
++ [[('*', 1)]
,[('0', 1)]
,[('#', 3)]
,[]
,[('#', 2)]
,[]
,[('0', 3)]
,[('1', 1)]
,[('2', 4)]
,[('3', 4)]
,[('4', 4)]
,[('5', 4)]
,[('6', 4)]
,[('7', 5)]
,[('8', 4)]
,[('9', 5)]]
++ replicate 7 []
++ [[('*', 1), ('2', 1)]
,[('*', 1), ('2', 2)]
,[('*', 1), ('2', 3)]
,[('*', 1), ('3', 1)]
,[('*', 1), ('3', 2)]
,[('*', 1), ('3', 3)]
,[('*', 1), ('4', 1)]
,[('*', 1), ('4', 2)]
,[('*', 1), ('4', 3)]
,[('*', 1), ('5', 1)]
,[('*', 1), ('5', 2)]
,[('*', 1), ('5', 3)]
,[('*', 1), ('6', 1)]
,[('*', 1), ('6', 2)]
,[('*', 1), ('6', 3)]
,[('*', 1), ('7', 1)]
,[('*', 1), ('7', 2)]
,[('*', 1), ('7', 3)]
,[('*', 1), ('7', 4)]
,[('*', 1), ('8', 1)]
,[('*', 1), ('8', 2)]
,[('*', 1), ('8', 3)]
,[('*', 1), ('9', 1)]
,[('*', 1), ('9', 2)]
,[('*', 1), ('9', 3)]
,[('*', 1), ('9', 4)]]
++ replicate 3 []
++ [[('*', 2)]]
++ replicate 2 []
++ [[('2', 1)]
,[('2', 2)]
,[('2', 3)]
,[('3', 1)]
,[('3', 2)]
,[('3', 3)]
,[('4', 1)]
,[('4', 2)]
,[('4', 3)]
,[('5', 1)]
,[('5', 2)]
,[('5', 3)]
,[('6', 1)]
,[('6', 2)]
,[('6', 3)]
,[('7', 1)]
,[('7', 2)]
,[('7', 3)]
,[('7', 4)]
,[('8', 1)]
,[('8', 2)]
,[('8', 3)]
,[('9', 1)]
,[('9', 2)]
,[('9', 3)]
,[('9', 4)]]
reverseTaps' :: Char -> [(Digit, Presses)]
reverseTaps' c = keyMap !! ord c
cellPhonesDead' :: String -> [(Digit, Presses)]
cellPhonesDead' s = concat $ map reverseTaps' s
convo :: [String]
convo =
["Wanna play 20 questions",
"Ya",
"U 1st haha",
"Lol ok. Have u ever tasted alcohol lol",
"Lol ya",
"Wow ur cool haha. Ur turn",
"Ok. Do u think I am pretty Lol",
"Lol ya",
"Haha thanks just making sure rofl ur turn"]
convoTaps = concat $ map cellPhonesDead' convo
fingerTaps :: [(Digit, Presses)] -> Presses
fingerTaps = foldr (\(d, p) s -> s + p) 0
convoFingerTaps = fingerTaps $ convoTaps
This is the beginning of my overengineered and likely to end up very stupid implementation of the phone: