Skip to content

Commit

Permalink
Preventing incorrect use (Modules, encapsulation and smart constructors)
Browse files Browse the repository at this point in the history
We made a few changes in these commits.

1. Moved the Html generation library to its own module
2. Added the escape function to escape characters

Each Haskell source file is a module. The module name should have the
same name as the source file and should start with a capital
letter. Sub-directories should also be part of the name and we use `.`
do denote a sub-directory. We'll see that in the next commit.

The only exception to the rule are entry points to the program -
modules with the name 'Main' that define `main` in them. They source
file names could have any name they want.

A module declaration looks like this:

```hs
module <module-name>
  ( <export-list>
  )
where
```

The export list can be omitted if you want to export everything
defined in the module, but we don't. We will list exactly the
functions and type we want to export.

Note that we do not export the constructors for our new types, only
the types themselves. If we wanted to export the constructors as well
we would've written `Html(Html)` or `Html(..)`.

Now, anyone importing our module (using the `import` statement which
can be used below module declarations but above any other
declaration), will only be able to import what we export.

We also add a new function, `escape`, which will convert usage of
characters that may conflict with our meta language html, with safer
options that will not.

See https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html

In `escape` we see two new things:

1. let expressions - we can define local names using this syntax:

```hs
let
  <name> = <expression>
in
  <expression>
```

This will make <name> available as a variable in the second <expression>.

2. Pattern matching with multiple patterns - we match on different
   characters and convert them to a string. Note that `_` is a "catch
   all" pattern that will always succeed.

3. Strings are linked lists of char - String is defined as:
   `type String = [Char]`, so we can use the same functions we used on
   lists, namely map and concat.

Now we can use our tiny html library safely. But what if the user
wants to use our library with something we didn't think about, for
example adding unordered lists? We are completely blocking them from
extending our library. We'll talk about this next.
  • Loading branch information
soupi committed Nov 7, 2020
1 parent e61c363 commit aeee32c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 40 deletions.
80 changes: 80 additions & 0 deletions Html.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module Html
( Html
, HtmlTitle
, HtmlBody
, HtmlBodyContent
, html_
, p_
, h1_
, render
)
where

-----------
-- Types --
-----------

newtype Html
= Html String

type HtmlTitle
= String

type HtmlBody
= [HtmlBodyContent]

newtype HtmlBodyContent
= HtmlBodyContent String

----------
-- EDSL --
----------

html_ :: HtmlTitle -> HtmlBody -> Html
html_ title content =
Html
( el "html"
( concat
[ el "head" (el "title" (escape title))
, el "body" (concat (map getBodyContentString content))
]
)
)

p_ :: String -> HtmlBodyContent
p_ = HtmlBodyContent . el "p" . escape

h1_ :: String -> HtmlBodyContent
h1_ = HtmlBodyContent . el "h1" . escape

------------
-- Render --
------------

render :: Html -> String
render (Html str) = str

-----------
-- Utils --
-----------

el :: String -> String -> String
el tag content =
"<" <> tag <> ">" <> content <> "</" <> tag <> ">"

getBodyContentString :: HtmlBodyContent -> String
getBodyContentString (HtmlBodyContent str) = str

escape :: String -> String
escape =
let
escapeChar c =
case c of
'<' -> "&lt;"
'>' -> "&gt;"
'&' -> "&amp;"
'"' -> "&quot;"
'\'' -> "&#39;"
_ -> [c]
in
concat . map escapeChar
45 changes: 5 additions & 40 deletions hello.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module Main where

import Html

main :: IO ()
main = putStrLn (render myhtml)

Expand All @@ -6,45 +10,6 @@ myhtml =
html_
"My title"
[ h1_ "Header"
, p_ "Paragraph #1"
, p_ "Paragraph #1 which may contain <html>code</html>"
, p_ "Paragraph #2"
]

newtype Html
= Html String

type HtmlTitle
= String

type HtmlBody
= [HtmlBodyContent]

newtype HtmlBodyContent
= HtmlBodyContent String

html_ :: HtmlTitle -> HtmlBody -> Html
html_ title content =
Html
( el "html"
( concat
[ el "head" (el "title" title)
, el "body" (concat (map getBodyContentString content))
]
)
)

p_ :: String -> HtmlBodyContent
p_ = HtmlBodyContent . el "p"

h1_ :: String -> HtmlBodyContent
h1_ = HtmlBodyContent . el "h1"

el :: String -> String -> String
el tag content =
"<" <> tag <> ">" <> content <> "</" <> tag <> ">"

getBodyContentString :: HtmlBodyContent -> String
getBodyContentString (HtmlBodyContent str) = str

render :: Html -> String
render (Html str) = str

0 comments on commit aeee32c

Please sign in to comment.