Flexible HTML content (functions)
We'd like to be able to write different HTML pages without having to write the whole structure of HTML and body tags over and over again. We can do that with functions.
To define a function, we create a definition as we saw previously and add the argument
names after the name and before the equals sign (=
).
So a function definition has the following form:
<name> <arg1> <arg2> ... <argN> = <expression>
The argument names will be available in scope on the right side of the equals sign
(in the <expression>
), and the function name will be <name>
.
We'll define a function that takes a string, which is the content of the page, and wraps it in
the relevant html
and body
tags by concatenating them before and after the content.
We use the operator <>
to concatenate two strings.
wrapHtml content = "<html><body>" <> content <> "</body></html>"
This function, wrapHtml
, takes one argument named content
and returns a string
that prefixes <html><body>
before the content and appends </body></html>
after it.
Note that it is common to use camelCase in Haskell for names.
Now we can adjust our myhtml
definition from the previous chapter:
myhtml = wrapHtml "Hello, world!"
Again, notice that we don't need parenthesis when calling functions. Function calls have the form:
<name> <arg1> <arg2> ... <argN>
However, if we wanted to substitute myhtml
with the expression myhtml
is bound
to in main = putStrLn myhtml
, we would have to wrap the expression in parenthesis:
main = putStrLn (wrapHtml "Hello, world!")
If we accidentally write this instead:
main = putStrLn wrapHtml "Hello, world!"
we'll get an error from GHC stating that putStrLn
is applied to two arguments,
but it only takes one. This is because the above is of the form <name> <arg1> <arg2>
in which, as we defined earlier, <arg1>
and <arg2>
are arguments to <name>
.
Using parenthesis, we can group the expressions together in the correct order.
An aside about operator precedence and fixity
operators (like
<>
) are infix functions that take two arguments - one from each side.When there are multiple operators in the same expression without parenthesis, the operator fixity (left or right) and precedence (a number between 0 and 10) determine which operator binds more tightly.
In our case,
<>
has right fixity, so Haskell adds an invisible parenthesis on the right side of<>
. So, for example:"<html><body>" <> content <> "</body></html>"
is viewed by Haskell as:
"<html><body>" <> (content <> "</body></html>")
For an example of precedence, in the expression
1 + 2 * 3
, the operator+
has precedence 6, and the operator*
has precedence 7, so we give precedence to*
over+
. Haskell will view this expression as:1 + (2 * 3)
You might run into errors when mixing different operators with the same precedence but different fixity, because Haskell won't understand how to group these expressions. In that case, we can solve the problem by adding parenthesis explicitly.
Exercises:
-
Separate the functionality of
wrapHtml
into two functions:- One that wraps content in
html
tag - one that wraps content in a
body
tag
Name the new functions
html_
andbody_
. - One that wraps content in
-
Change
myhtml
to use these two functions. -
Add another two similar functions for the tags
<head>
and<title>
and name themhead_
andtitle_
. -
Create a new function,
makeHtml
, which takes two strings as input:- One string for the title
- One string for the body content
And construct an HTML string using the functions implemented in the previous exercises.
The output for:
makeHtml "My page title" "My page content"
should be:
<html><head><title>My page title</title></head><body>My page content</body></html>
-
Use
makeHtml
inmyhtml
instead of usinghtml_
andbody_
directly
Solutions:
Solution for exercise #1
html_ content = "<html>" <> content <> "</html>"
body_ content = "<body>" <> content <> "</body>"
Solution for exercise #2
myhtml = html_ (body_ "Hello, world!")
Solution for exercise #3
head_ content = "<head>" <> content <> "</head>"
title_ content = "<title>" <> content <> "</title>"
Solution for exercise #4
makeHtml title content = html_ (head_ (title_ title) <> body_ content)
Solution for exercise #5
myhtml = makeHtml "Hello title" "Hello, world!"
Our final program
-- hello.hs
main = putStrLn myhtml
myhtml = makeHtml "Hello title" "Hello, world!"
makeHtml title content = html_ (head_ (title_ title) <> body_ content)
html_ content = "<html>" <> content <> "</html>"
body_ content = "<body>" <> content <> "</body>"
head_ content = "<head>" <> content <> "</head>"
title_ content = "<title>" <> content <> "</title>"
We can now run our hello.hs
program, pipeline the output into a file,
and open it in our browser:
runghc hello.hs > hello.html
firefox hello.html
It should display Hello, world!
on the page and Hello title
on the page's title.
Indentation
You might ask how does Haskell know a definition is complete? The answer is: Haskell uses indentation to know when things should be grouped together.
Indentation in Haskell can be a bit tricky, but in general: code that is supposed to be part of some expression should be indented further than the beginning of that expression.
We know the two definitions are separate because the second one is not indented further than the first one.
Indentation tips
- Choose a specific amount of spaces for indentation (2 spaces, 4 spaces, etc.) and stick to it. Always use spaces over tabs.
- Do not indent more than once at any given time.
- When in doubt, drop the line as needed and indent once.
Here are a few examples:
main =
putStrLn "Hello, world!"
or:
main =
putStrLn
(wrapHtml "Hello, world!")
Avoid the following styles, which use more than one indentation step, or completely disregard indentation steps:
main = putStrLn
(wrapHtml "Hello, world!")
main = putStrLn
(wrapHtml "Hello, world!")