46
Building a website in Haskell coming from Node.js @nicolas_hery

Building a website in Haskell coming from Node.js

Embed Size (px)

Citation preview

Building a websitein Haskell

comingfrom Node.js

@nicolas_hery

I'm a web developer

I work at Busbudon our website

we are a JavaScriptshop (Node.js, React)

interested in

FunctionalProgramming

make API/DB calls, put data in HTML, respond

to HTTP requests

work on a team, modify other people's code

constantly adapt to business change

a web developer in a startup needs to...

Was it hard challenging? Yes

Was it possible? Yes

Would I do it again? Yes!

my experiencebuilding something in Haskell

The app

port existing JavaScript app to Haskell

Jumping in

choosing a web framework

Yesodbattle-testedgreat docsbig framework

Scottylightweightfamiliarnot a lot of docs

Servantleverages typesmost differentfairly new

Code examples

typesdocument your

domain data

function renderCharacterDetails(character) {

// what is the shape of `character`?

};

data Character = Character

{ id :: CharacterId

, name :: Text

, description :: Maybe Text

, urls :: [Url]

, thumbnail :: Image

, comics :: ComicList

}

renderCharacterDetails :: Character -> Html

renderCharacterDetails character = -- ...

var t = require('tcomb');

var Character = t.struct({

id: t.Number,

name: t.String,

description: t.maybe(t.String),

urls: t.list(Url),

thumbnail: Image,

comics: ComicList

}, 'Character');

function renderCharacterDetails(character) {

// ...

};

Maybe/Eitherforce you to

handle possible cases(missing data, errors)

Character.getMarvelUrl = function(character) {

// will this always return a string?

return character.urls.find(function(url) {

return url.type === 'detail';

}).url;

};

getMarvelUrl :: Character -> Text

getMarvelUrl character =

let defaultUrl = "http://marvel.com"

maybeUrl = find isDetailUrl (urls character)

in case maybeUrl of

Nothing -> defaultUrl

Just detailUrl -> U._url detailUrl

some things that

are trivial in JavaScript

are hard take longer in Haskell(ex: IO)

function getCharactersController(req, res, next) {

var offset = req.query.offset;

// ...

}

GET /characters?offset=20(offset is optional)

getCharactersController :: HandlerM ()

getCharactersController = do

offset :: Int <- param "offset"

`rescue` (\_ -> return 0)

-- ...

explicit JSON decodingchallenge when working with more

complex data structures

artyom.me/aeson

{

"code": 200,

"status": "Ok",

"data": {

"total": 1,

"count": 1,

"results": [

{

"id": 1009610,

"name": "Spider-Man",

// ...

}

]

}

}

GET /api/characters/1009610

var body = JSON.parse(response);

var character = Character(body.data.results[0]);

return {

character: character

};

data CharacterResponse = CharacterResponse

{ character :: Character }

instance FromJSON CharacterResponse where

parseJSON (Object o) = do

_data <- o .: "data"

_results :: [Character] <- _data .: "results"

case _results of

[] -> fail "data.results should be non-empty"

(x:_) -> return CharacterResponse { character=x }

parseJSON _ = mzero

refactoring

is easy

data Comic = Comic

{ id :: ComicId

- , title :: Text

+ , name :: Text

, description :: Maybe Text

, urls :: [Url]

$ stack build

Views/Components/ComicsList.hs:23:57:

Not in scope: ‘C.title’

Views/Components/ComicDetails.hs:29:31:

Not in scope: ‘C.title’

Views/Components/ComicDetails.hs:30:33:

Not in scope: ‘C.title’

Views/Pages/Comic.hs:27:50:

Not in scope: ‘C.title’

Controllers/Comic.hs:71:23:

Not in scope: ‘C.title’

Shipping

Docker & Docker Composedocs.docker.com/compose/install

Heroku Toolbelt & Heroku Docker Plugindevcenter.heroku.com/articles/docker

Dockerfilehub.docker.com/r/thoughtbot/heroku-haskell-stack

Heroku Docker & Stack

$ heroku docker:release

observations

the Good

if it compiles

it works

optimize

for change

type-safe URLstype-safe i18ntype-safe JS hooks

focus on logical errors

versus malformed code

influence the way you write JavaScript

observations

the Bad & the Ugly

much syntax

haskellforall.com/2015/09/how-to-make-your-haskell-code-more.html

. $ !! @ <$> <*> <- >> >>= & ^. ^.. %~ .~ =~ ¯\_(ツ)_/¯

implicit versus

explicit and qualified importsimport Data.Text.Lazy

import Network.HTTP.Types

import Text.Blaze.Html.Renderer.Text

import Web.Scotty.Trans

import qualified Data.Text.Lazy as TL

import Network.HTTP.Types (status404, status500)

import Text.Blaze.Html.Renderer.Text (renderHtml)

import Web.Scotty.Trans (ActionT, html, param, status)

record labels can cause

naming conflictsimport qualified Models.Character as C

import qualified Models.ComicSummary as CS

characterDetailsView :: Character -> Html

characterDetailsView character =

-- ...

H.img ! A.alt (toValue (C.name character))

-- ...

H.li $ toHtml (CS.name comicSummary)

danger of too much abstraction

www.yesodweb.com/blog/2015/10/beginner-friendly-code-and-apis

someFunc =

maybe getDefaultValue otherFunc . flip lookup someMap

someFunc key =

case lookup key someMap of

Nothing -> getDefaultValue

Just value -> otherFunc value

documentation

often lack examples(type signatures not enough for beginners)

exceptions:www.yesodweb.com/bookgithub.com/Gabriel439/Haskell-Pipes-Librarygithub.com/Gabriel439/Haskell-Turtle-Library...

Wrap up

google "startup blog haskell"

www.wagonhq.com

www.frontrowed.com

...

are people actually doing this?

some

closing thoughts

learn the basics and then build something

don't try to learn everything at once

don't be afraid to ask for help