A general purpose programming language. It is used for:
The most popular and active compiler for Haskell is GHC, which is written in Haskell as well.
Do you:
If so, consider learning Haskell. It's fun, it's practical, it's educational.
*.hs
) file structurestack setup
to setup a Haskell environmentMain.hs
and work there for the rest of the exercisesgit clone https://github.com/soupi/haskell-workshop.git
cd haskell-workshop
stack runghc Main.hs
<name> = <expression>
5
(2 * 3) - (4 + 5)
five = 5
negThree = (2 * 3) - (4 + 5)
We can give a name to a computation in a computation using let
comp =
let
left = 2 * 3
right = 4 + 5
in
left - right
We can give a name to a computation in a computation using where
comp =
left - right
where
left = 2 * 3
right = 4 + 5
=
means that both sides are interchangeablex = 5
y =
-- shadowing x in the scope of `let ... in ...`
let
x = 6
in
x + x
z = x + y
What's the value of z
? Why?
A simple rule of thumb: Code which is part of some expression should be indented further in than the beginning of that expression
type <name> = <type>
Integer
, Bool
, String
(String, Bool)
[Integer]
type Value = Integer
val :: Value
val = 777
Note:
::
to declare the type of a name=
to declare the value of a namegil'sFavoritefruits :: [String]
gil'sFavoriteFruits =
["Orange", "Banana", "Kiwi", "Avocado", "Mango"]
gil'sNameAndAge :: (String, Integer)
gil'sNameAndAge = ("Gil", 28)
Note:
'
is a valid character in a binding name
gil'sNameAndAge :: (String, Integer)
gil'sNameAndAge =
( "Gil"
, 28
)
gil'sFavoritefruits :: [String]
gil'sFavoriteFruits =
[ "Orange"
, "Banana"
, "Kiwi"
, "Avocado"
, "Mango"
]
Define a type which represents a database table.
Integer
sDefine a sample table:
"x"
and "y"
Like value declarations, just add argument names before the =
<function-name> <arg1> <arg2> ... <argN> = <expression>
increment n = n + 1
six = increment five
seven = increment (increment five)
incAndAdd x y = increment x + increment y
factorial n =
if n < 1
then n
else n * factorial (n - 1)
Check and pretty-print a table:
-- takes 3 arguments, so in this case N = 3
sum3 x y z = x + y + z
-- only supplies 2 arguments (K = 2), 0 and 1.
-- so newIncrement is a function that takes (N - K = 1) arguments
newIncrement = sum3 0 1
-- three is the value 3
three = newIncrement 2
compose f g x = f (g x)
-- evaluates to 8
eight = compose double increment 3
-- evaluates to [2,3,4,5]
twoThreeFourFive = map increment [1,2,3,4]
apply x f = f x
-- evaluates to [4,6,10]
fourSixTen = map (apply 5) [decrement, increment, double]
map
is left as an exercise to the reader
type
for a database as an association list (a list of tuples of keys and values) of tables and table namesDatabase
valueDefine a myMap
function.
Use:
null
to check for the empty listhead
and tail
to get the first value and the rest of the values of a list:
to "add" a value to the start of a listppTableNames
function, which return a string which is the names of the tables in a databasegetName
intercalate ", "
, unwords
or unlines
to concat a list of strings to a string<-
is used to bind the result of an IO action to a variable when using do notationlet
is used to bind an expression to a name (note: no need to write the accompanied in
)main = do
putStrLn "Hello!"
putStrLn "What is your name?"
result <- getLine
putStrLn ("Nice to meet you, " ++ result)
putStrLn "Here is the result of 1+1: "
let calculation = factorial 100 -- no need for in
putStrLn (show calculation)
putStrLn "Bye!"
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
putStrLn ("Nice to meet you, " ++ getLine)
Hello.hs:6:37: error:
• Couldn't match expected type ‘[Char]’
with actual type ‘IO String’
• In the first argument of ‘(++)’, namely ‘getLine’
In the second argument of ‘(++)’, namely ‘getLine’
In the expression: "Nice to meet you, " ++ getLine
|
6 | putStrLn ("Nice to meet you, " ++ getLine)
| ^^^^^^^
IO String
in place of String
String
is defined as type String = [Char]
String
which was expected, with IO String
which is the type of getLine
IO a
and a
are different typesmodule Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
putStrLn "Nice to meet you, " ++ name
Hello.hs:7:3: error:
• Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
• In the first argument of ‘(++)’, namely
‘putStrLn "Nice to meet you, "’
In a stmt of a 'do' block:
putStrLn "Nice to meet you, " ++ name
In the expression:
do putStrLn "Hello! What is your name?"
name <- getLine
putStrLn "Nice to meet you, " ++ name
|
7 | putStrLn "Nice to meet you, " ++ name
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Parenthesis are needed around the expression string
putStrLn ("Nice to meet you, " ++ name)
prompt
IO action that:lookup
)lookup
returns a value of the type Maybe Table
isJust
to check if it's a table or notfromJust
to cast it to a table if it's foundLet's talk about types and how to represent a query in Haskell
type
to give a new alias to an existing type. They can be used interchangingly.type Username = String
We can give values a type signature using ::
myUsername :: Username
myUsername = "soupi"
data
|
to say "alternatively"data KnownColor -- the new type's name
= Red -- One possible value
| Blue
| Green
redColor :: KnownColor
redColor = Red
data
to define compound data of existing typesdata RGB
= MkRGB Int Int Int
{-
^ ^ ^ ^
| | | |
| | | +- This is the blue component
| | |
| | +----- This is the green component
| |
| +--------- This is the red component
|
+------------- This is called the value constructor, or "tag"
-}
magenta :: RGB
magenta = MkRGB 255 0 255
Red
, Blue
, Green
or RGB
) create a value of the typeRGB
), value constructors can be used as regular functions to build values of the typedata Color
= Red
| Blue
| Green
| RGB Int Int Int
blue :: Color
blue = Blue
magenta :: Color
magenta = RGB 255 0 255
data RGB = MkRGB
{ rgbRed :: Int
, rgbGreen :: Int
, rgbBlue :: Int
}
red :: RGB
red = MkRGB
{ rgbRed = 255
, rgbGreen = 0
, rgbBlue = 0
}
Define a Query
data type with two value constructors:
Table
- which is a name of a table in the databaseValues
- which is an in-place table valueThis value constructors represents an SQL data source query
-- This is like our value constructor, we write a table (list of rows) in-line
INSERT INTO t VALUES (1,2,3),(4,5,6);
-- This is like our `Table` value constructor,
-- We write a table name to reference a table in the database
SELECT * FROM t;
Add the line deriving (Show, Read)
at the end, this will enable us to use show
(and print
) and read
on our Query
data structure.
We use ->
to denote the type of a function from one type to another type
increment :: Int -> Int
increment n = n + 1
sum3 :: Int -> Int -> Int -> Int
sum3 x y z = x + y + z
supplyGreenAndBlue :: Int -> Int -> Color
supplyGreenAndBlue = RGB 100
->
is right associative, The function definitions from the previous slide will be parsed like this:
increment :: Int -> Int
increment n = n + 1
sum3 :: (Int -> (Int -> (Int -> Int)))
sum3 x y z = x + y + z
supplyGreenAndBlue :: (Int -> (Int -> Color))
supplyGreenAndBlue = RGB 100
This is why partial function application works.
-- I only take concrete `Int` values
identityInt :: Int -> Int
identityInt x = x
five :: Int
five = identityInt 5
-- `a` represents any one type
identity :: a -> a
identity x = x
seven :: Int
seven = identity 7
true :: Bool
true = identity True
const :: a -> b -> a
const x y = x
-- will fail because nothing in the type signature suggests that
-- `a` and `b` necessarily represent the same type
identity1 :: a -> b
identity1 x = x
-- will fail because we don't know if `a` is `Int`
identity2 :: a -> Int
identity2 x = x
-- will fail because we don't know if `a` is `Int`
identity3 :: Int -> a
identity3 x = x
compose :: (b -> c) -> (a -> b) -> a -> c
compose f g x = f (g x)
f . g = compose f g
->
in type signatures is right associativecompose :: ((b -> c) -> ((a -> b) -> (a -> c)))
compose f g x = f (g x)
Go back to the functions you defined and create a type signature for them
data IntList
= EndOfIntList
| ValAndNext Int IntList
-- the list [1,2,3]
list123 :: IntList
list123 = ValAndNext 1 (ValAndNext 2 (ValAndNext 3 EndOfList))
data IntTree
= Leaf
| Node
IntTree -- Left subtree
Int -- Node value
IntTree -- Right subtree
-- 2
-- / \
-- 1 3
-- /
-- 1
tree1123 :: IntTree
tree1123 =
Node
(Node (Node Leaf 1 Leaf) 1 Leaf)
2
(Node Leaf 3 Leaf)
Int
or Bool
like in the previous slide-- a value of type a or nothing
data Maybe a
= Just a
| Nothing
-- a value of type a or a value of type b
data Either a b
= Left a
| Right b
-- A linked list of `a`s
-- Note: there's also a built in syntax in Haskell for linked lists
data List a -- [a] -- special syntax for a linked list of a generic type `a`
= Nil -- [] -- special syntax for the empty list
| Cons a (List a) -- x : xs -- special operator for constructing a list
To your Query
data type, add the a Select
option with the following arguments:
This represents an SQL SELECT
statement
SELECT x as x1, x as x2, y as y -- the select list
FROM t; -- the inner query (a table named 't')
case <expr> of
<pattern1> -> <result1>
<pattern2> -> <result2>
...
<patternN> -> <resultN>
myIf :: Bool -> a -> a -> a
myIf test trueBranch falseBranch =
case test of
True -> trueBranch
False -> falseBranch
factorial :: Int -> Int
factorial num =
case num of
0 -> 1
n -> n * factorial (n - 1)
_
means match anythingcolorName :: Color -> String
colorName color =
case color of
Red -> "red"
Green -> "green"
Blue -> "blue"
RGB 255 0 255 -> "magenta"
RGB _ 255 _ ->
"well it has a lot of green in it"
RGB _ 255 n ->
"It has a lot of green and " ++ show n ++ " in the blue component"
_ -> "i don't know this color"
Before jumping into writing a query engine, let's first write a direct-style interpreter for the following data type:
data Expr
= Value Integer
| Add Expr Expr
| Mul Expr Expr
This type represents a simple mathematical expression.
Write a function eval :: Expr -> Int
that calculates the result.
Write an interpreter for our Query
data type
Database
to lookup tablesClues:
Select
, You'll need to use map
(or myMap
). In two different ways.lookupCol
to search a value of a column in a row by the column's nameerror "Lookup failed"
to terminate earlyCreate an IO action that reads a query of a user, interprets it and displays it back
Clues:
readQuery
to parse the query from the userrepl
with your IO action as a parameters to create a REPL.Query
in the prompt to interpret it. For example: Select [("x", "x")] (Values [[("x",1)]])
.Union
constructor for our Query
data type and handle it in the interpreterThis constructor represents a UNION
operator in SQL
SELECT x as x1, y as y -- the select list
FROM
t1 UNION t2 -- the inner queries (two tables named 't1' and 't2')
;
Restrict
constructor for our Query
data type and handle it in the interpreterfilter
which takes a predicate and a list and returns a list that contains only values that satisfy the predicateThis constructor represents a where
clause in SQL
SELECT x as x1, x as x2, y as y -- the select list
FROM t -- the inner query (a table named 't')
where y = 1
;