SERVER
Is the browser IE?
Request
YES
REJECT THE REQUEST
SERVE NORMAL CONTENT
NO
app.get("/", (req, res) => {
if(isUsingIE(req)) {
res.status(400)
.send("Use a true browser!")
} else { res.render("index.ejs"); }
});
app.get("/", (req, res) => {
if(isUsingIE(req)) {
res.status(400)
.send("Use a true browser!")
} else { res.render("index.ejs"); }
});
app.get("/haskell", (req, res) => {
if(isUsingIE(req)) {
res.status(400)
.send("Use a true browser!")
} else { res.render("haskell.ejs"); }
});
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
FRAMEWORK
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
FRAMEWORK
USER CODE
MIDDLEWARE
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
FRAMEWORK
USER CODE
MIDDLEWARE
REJECT THE REQUEST
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
FRAMEWORK
USER CODE
MIDDLEWARE
REJECT THE REQUEST
SERVER
Is the browser IE?
Request
YES
REJECT THE REQUEST
SERVE NORMAL CONTENT
NO
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
FRAMEWORK
USER CODE
MIDDLEWARE
REJECT THE REQUEST
/haskell
/
Welcome to my amazing website!
Haskell is amazing!
USER CODE
USER CODE
REJECT THE REQUEST
USER CODE
If the browser is IE
Is the browser IE?
Request
YES
REJECT THE REQUEST
NO
Is there an \(\texttt{auth}\) cookie?
NO
LOGIN PAGE
Is the browser IE?
Request
YES
REJECT THE REQUEST
NO
Is there an \(\texttt{auth}\) cookie?
NO
LOGIN PAGE
YES: User
Â
Â
ROUTER
Â
Â
Â
Just Home
/
Just Haskell
/haskell
POST /haskell
referer: lambda.org
Â
{ "message": "hello",
 "dest": "Evan" }
Just ( SendMessage
  { to = "Evan"
  , content = "Hello"
  , sentFrom ="lambda.org"
  } )
Nothing
Is the browser IE?
Request
YES
REJECT THE REQUEST
NO
Is there an \(\texttt{auth}\) cookie?
NO
LOGIN PAGE
YES: User
Â
Â
ROUTER
Â
Â
Â
Just Home
/
Just Haskell
/haskell
POST /haskell
referer: lambda.org
Â
{ "message": "hello",
 "dest": "Evan" }
Just ( SendMessage
  { to = "Evan"
  , content = "Hello"
  , sentFrom ="lambda.org"
  } )
Nothing
serveStatic "Welcome!"
serveStatic "Haskell!"
send { to = "Evan", from = User
     , content = "Hello" };
log "Message sent from: lambda.org"
notFound
SĂ©bastien Besnier
@_sebbes_
Programming language
designed for front end development
17k LoC
200k LoC
A language with no runtime exception
Â
Check it out:
694
elm
init
reactor
install
make
publish
repl
Â
a tiny dev server
add new dependencies
download deps, compile, build really quickly
packages
Â
Â
We have newcomers go through the official Elm guide, we sit down with them to answer questions and do concrete work, and after a couple of weeks, we don’t get requests for help anymore.
Â
https://www.humio.com/whats-new/blog/why-we-chose-elm-for-humio-s-web-ui
We have newcomers go through the official Elm guide, we sit down with them to answer questions and do concrete work, and after a couple of weeks, we don’t get requests for help anymore.
Â
https://www.humio.com/whats-new/blog/why-we-chose-elm-for-humio-s-web-ui
A beginner Elm developer, in our experience, can be productive in a couple of weeks and can master the language in a couple of months.
We have newcomers go through the official Elm guide, we sit down with them to answer questions and do concrete work, and after a couple of weeks, we don’t get requests for help anymore.
Â
https://www.humio.com/whats-new/blog/why-we-chose-elm-for-humio-s-web-ui
A beginner Elm developer, in our experience, can be productive in a couple of weeks and can master the language in a couple of months.
Several rounds of summer interns have also proven that it is possible to learn Elm and our systems, and become productive in a matter of days.
Â
REPL demo!
text : String -> Html
node :
String
-> List Attributes
-> List Html
-> Html
node tag attributes children =
...
TWO BUILDING BLOCKS
node "p" [class "btn"] [ text "hello"]
<p class="btn">Hello</p>
text : String -> Html
node :
String
-> List Attributes
-> List Html
-> Html
node tag attributes children =
...
p = node "p"
ul = node "ul"
div = node "div"
...
p [class "btn"] [ text "hello"]
<p class="btn">Hello</p>
viewKids kids =
ul []
(List.map
(\kid -> li [] [ text kid ])
kids
)
Â
It's just a map!
Templating language demands:
Templating language demands:
Free/useless if you directly use your language with text and node!
<html><head>
<link href="/my-style.css">
<script src="main.js"></script>
</head>
<body>
<div id="myapp"></div>
<script>
var app = Elm.Main.init({
node: document.getElementById('myapp')
});
</script>
</body></html>
<html><head>
<link href="/my-style.css">
<script src="main.js"></script>
</head>
<body>
<div id="myapp"></div>
<script>
var app = Elm.Main.init({
node: document.getElementById('myapp')
});
</script>
</body></html>
-- Main.elm
p [ class "my-btn"
, style "color" "red"
]
[ text "hello"]
Some Issues when dealing with External CSS:
Â
Some Issues when dealing with External CSS:
Let's use our existing compiler tackle all of that!
-- Main.elm
div []
[ p [ css
[ backgroundColor (rgb 255 0 255)
, padding (px 30) ]
]
[ text "hello" ]
]
<div>
<style>._72f9689 {
background-color:rgb(255, 0, 255);
padding:30px;
}
</style>
<p class="_72f9689">hello</p>
</div>
-- Main.elm
div []
[ p [ css
[ backgroundColor (rgb 255 0 255)
, padding (px 30) ]
]
[ text "hello" ]
]
-- Main.elm
div []
[ p [ buttonStyle ] [ text "hello" ]
, p [ buttonStyle ] [ text "good bye" ]
]
buttonStyle =
css
[ backgroundColor (rgb 255 0 255)
, padding (px 30)
]
Some Issues when dealing with External CSS:
Let's use our existing compiler tackle all of that!
Organize the style as normal code!
Some Issues when dealing with CSS:
Â
Some Issues when dealing with CSS:
Â
text : String -> Element
row : List Attribute
-> List Element
-> Element
column : List Attribute
-> List Element
-> Element
layout : List Attribute
-> List Element
-> Html
text : String -> Element
row : List Attribute
-> List Element
-> Element
column : List Attribute
-> List Element
-> Element
layout : List Attribute
-> List Element
-> Html
button :
List Attribute
->
{ onPress : Msg
, label : Element
}
-> Element
row [ centerY, centerX, width fill ]
[ text "Home"
, el [ centerX ] (text "Lambda Lille")
, el [ alignRight ] (text "Connexion")
]
Text
Lambda Lille
Connexion
Home
type alias Person =
{ name : String
, age: Int
}
view : Person -> Html
view p =
text p.name
-- FUNCTION CALL:
view { name = "Seb", age = 42 }
List.map .age
[{ name = "Seb", age = 42 }
,{ name = "Bob", age = 53 } ]
-- RESULT:
[ 42, 53 ]
-- SIGNATURE:
.age : { anyRecord | age : value } -> value
Displaying depends on screen size.
view model =
case model.screenWidth of
Phone -> viewPhone model.page
Tablet -> viewTable model.page
Desktop -> viewDesktop model.page
BigDesktop -> viewBigDesktop model.page
Displaying depends on screen size.
Let's make that explicit!
Side effects are totally "controlled"
Side effects are totally "controlled"
Side effects are totally "controlled"
Side effects are totally "controlled"
IO
Side effects are totally "controlled"
IO
List.head []
Binding to C (it is even "easy")
Side effects are totally "controlled"
IO
List.head []
Binding to C (it is even "easy")
init : Model
view : Model -> Html Msg
update : Msg -> Model -> Model
init : Model
view : Model -> Html Msg
update : Msg -> Model -> (Model, Cmd Msg)
Commands (Cmd) are "requests" to (e.g):
activateCmd : Cmd Msg
activateCmd = post
{ url = "/activate"
, expect =
expectWhatever GotActivationResult
, body = emptyBody
}
Commands (Cmd) are "requests" to (e.g):
activateCmd : Cmd Msg
activateCmd = post
{ url = "/activate"
, expect =
expectWhatever GotActivationResult
, body = emptyBody
}
Msg generated when response received
type Msg
= ButtonClicked
| GotActivationResult (Result Error ())
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
ButtonClicked ->
(model, activateCmd)
GotActivationResult (Ok ()) ->
({model | message = "Activated!"}
, Cmd.none
)
GotActivationResult (Err _) ->
({model | message = "Error :/"}
, Cmd.none
)
Commands (Cmd) are "requests" to (e.g):
Time.now : (Posix -> msg) -> Cmd msg
Time.here : (Zone -> msg) -> Cmd msg
Commands (Cmd) are "requests" to (e.g):
Commands (Cmd) are "requests" to (e.g):
port copyToClipboard : String -> Cmd msg
update msg model =
case msg of
CopyButtonClicked ->
( model
, copyToClipboard "Lorem Ipsum"
)
const app = Elm.Main.init({
node: document.getElementById('root'),
});
app.ports.copyToClipboard.subscribe(
content =>
navigator.clipboard.writeText(content)
);
index.js
Cmd
Cmd
Sub
It's just message exchange!
Continuously send volume
Ports?
Continuously send volume
Ports?
Please, start sending volume
Please, stop sending volume
function createMicListener(
callback: (volume: number) => void
): { disconnect: () => void }
{ /*...*/}
index.js
function createMicListener(
callback: (volume: number) => void
): { disconnect: () => void }{ /*...*/}
customElements.define("audio-meter",
class extends HTMLElement {
connectedCallback() {
this.micListener = createMicListener((volume)=>{
this.dispatchEvent(new CustomEvent(
"volume",{"detail": volume}
));
});
}
disconnectedCallback() {
this.micListener.disconnect();
}
}
);
index.js
import Json.Decode as D
view model =
if model.isRecording then
div []
[ node "audio-meter"
[ on "volume"
(D.field "detail" (D.map GotVolume float))
]
, text ("Volume:"++ Str.fromFloat model.volume)
]
[]
else
text "Not listening"
update msg model =
case msg of
GotVolume volume ->
{ model | volume = volume }
Custom Elements for:
Side effects are totally "controlled"
IO
List.head []
Binding to C (it is even "easy")
Elm Architecture
Ports for FFI
Side effects are totally "controlled"
IO
Elm Architecture
Ports for FFI
Custom Elements
-- run arbitrary code with HTML type
List.head []
Binding to C (it is even "easy")
{
"name": "Super Camp",
"duration": 7.0,
"kids": ["SĂ©bastien", "Thomas"]
}
JSON
import Json.Decode as D
type alias Camp =
{ name : String
, kids : List String
, duration : Float
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.field "name" D.string)
(D.field "kids" (D.list D.string))
(D.field "duration" D.float)
import Simple.JSON as JSON
type Camp =
{ name : String
, kids : List String
, duration : Float
}
main = do
case JSON.readJSON theJSON of
Right (camp :: Camp) -> do
-- Do stuff with camp!
Left e -> do
-- Handle failre
{
"name": "Super Camp",
"duration": 7.0,
"kids": ["SĂ©bastien", "Thomas"]
}
JSON
{
"name": "Super Camp",
"duration": 7.0,
"children": ["SĂ©bastien", "Thomas"]
}
JSON
Multiple options:
import Json.Decode as D
type alias Camp =
{ name : String
, kids : List String
, duration : Float
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.field "name" D.string)
(D.field "children" (D.list D.string))
(D.field "duration" D.float)
import Json.Decode as D
type alias Camp =
{ name : String
, kids : List String
, duration : Float
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.field "name" D.string)
(D.field "children" (D.list D.string))
(D.field "duration" D.float)
Decoders define Explicit Boundaries with the Outside
{
"name": "Super Camp",
"duration": 7.0,
"children": ["SĂ©bastien", "Thomas"]
}
JSON
{ "metadata": {
"name": "Super Camp",
"place": "Paris"
},
"duration": 7.0,
"children": [
{ "name": "SĂ©bastien", "age": 42},
{ "name": "Thomas", "age": 42}
]
}
JSON
import Json.Decode as D
type alias Camp =
{ name : String
, kids : List String
, duration : Float
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.at [ "metadata", "name" ] D.string)
(D.field "children" (D.list
(D.field "name" D.string)
))
(D.field "duration" D.float)
succeed : a -> Decoder a
field : String -> Decoder a -> Decoder a
string : Decoder String
int : Decoder Int
list : Decoder a -> Decoder (List a)
map2 :
(a -> b -> c)
-> Decoder a
-> Decoder b
-> Decoder c
succeed : a -> Decoder a
field : String -> Decoder a -> Decoder a
string : Decoder String
int : Decoder Int
list : Decoder a -> Decoder (List a)
map2 :
(a -> b -> c)
-> Decoder a
-> Decoder b
-> Decoder c
COMBINATORS
import Json.Decode as D
type alias Camp =
{ name : String
, kids : List String
, duration : Float
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.at [ "metadata", "name" ] D.string)
(D.field "children" (D.list
(D.field "name" D.string)
))
(D.field "duration" D.float)
import Json.Decode as D
import Duration exposing (Duration)
type alias Camp =
{ name : String
, kids : List String
, duration : Duration
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.at [ "metadata", "name" ] D.string)
(D.field "children" (D.list
(D.field "name" D.string)
))
(D.field "duration"
(D.map Duration.weeks D.float))
import Json.Decode as D
import Duration exposing (Duration)
type alias Camp =
{ name : String
, kids : List String
, duration : Duration
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.at [ "metadata", "name" ] D.string)
(D.field "children" (D.list
(D.field "name" D.string)
))
(D.field "duration"
(D.map Duration.weeks D.float))
weeks : Float -> Duration
import Json.Decode as D
import Duration exposing (Duration)
type alias Camp =
{ name : String
, kids : List String
, duration : Duration
}
campDecoder : D.Decoder Camp
campDecoder =
D.map3 Camp
(D.at [ "metadata", "name" ] D.string)
(D.field "children" (D.list
(D.field "name" D.string)
))
(D.field "duration"
(D.map Duration.weeks D.float))
weeks : Float -> Duration
👆 Click me 👆
{
"name": "Super Camp",
"duration": 7.0,
"children": ["SĂ©bastien", "Thomas"]
}
query {
human(id: "1001") {
name
homePlanet
}
}
query : SelectionSet (Maybe Human) RootQuery
query =
Query.human { id = Id "1001" } humanSelection
type alias HumanData =
{ name : String, homePlanet : Maybe String }
humanSelection : SelectionSet Human StarWars.Object.Human
humanSelection =
SelectionSet.map2 HumanData
Human.name
Human.homePlanet
Entirely type safe requests
JSON decoding example:
Yeah!
JSON decoding example:
How to find the implementation?
JSON decoding example:
buttonDecoder : ButtonType -> Decoder Button
JSON decoding example:
buttonDecoder : ButtonType -> Decoder Button
Rigidity
Rigidity
Compilation time
Yeah!
How to find the implementation?
Yeah!
How to find the implementation?
Code reuse
Abstraction
Rigidity
Compilation time
Rigidity
Compilation time
Yeah!
How to find the implementation?
Code reuse
Abstraction
Cognitive & Skills overloading
No Type Classes in Elm
(or in a really limited form)
No Type Classes in Elm
(or in a really limited form)
We have newcomers go through the official Elm guide, we sit down with them to answer questions and do concrete work, and after a couple of weeks, we don’t get requests for help anymore.
Â
https://www.humio.com/whats-new/blog/why-we-chose-elm-for-humio-s-web-ui
elm
make
download deps, compile, build really quickly
No Type Classes in Elm
(or in a really limited form)
Related:
module Page.Team exposing
(Model, Locale, view, ...)
type alias Locale =
{ team : String }
view : Locale -> Model -> Html Msg
view locale model =
Html.text locale.team
module Lang.Fr exposing (locale)
import Global
import Page.Team
import Page.Book
locale : Global.Local
locale =
{ login = "Connexion"
, logout = "DĂ©connexion"
, teamPage = teamPage
, bookPage = bookPage
}
teamPage : Page.Team.Locale
teamPage =
{ team = "Équipe!" }
bookPage : Page.Book.Locale
bookPage =
{ book = "Livre!" }
module Lang.Fr exposing (locale)
import Global
import Page.Team
import Page.Book
locale : Global.Local
locale =
{ login = "Connexion"
, logout = "DĂ©connexion"
, teamPage = teamPage
, bookPage = bookPage
}
teamPage : Page.Team.Locale
teamPage =
{ team = "Équipe!" }
bookPage : Page.Book.Locale
bookPage =
{ book = "Livre!" }
Let users write rules PER PROJECT
E.g. forbid the use of \(\texttt{Html.button}\) and force the use of \(\texttt{Theme.button}\)
Some "global" rules
type SomeType
= Used
| Unused -- line can be removed
defaultValue = Used
toString : SomeType -> String
toString value =
case value of
Used -> "used"
Unused -> "unused" -- line can be removed
Some "global" rules
type Effect
= RequestUsersList
| Toast String
perform : Effect -> Model -> (Model, Cmd Msg)
update msg model =
let
(newModel, eff) =
case newModel.page of
Page1 -> myUpdate1 msg model
Page2 -> myUpdate2 msg model
in
perform eff newModel
myUpdate1 : Msg -> Model -> (Model, Effect)
myUpdate2 : Msg -> Model -> (Model, Effect)
type alias Layout msg =
{ mainPanel : Html msg, sidebar : Sidebar }
myView1 : Model -> Layout msg
myView2 : Model -> Layout msg
render : Layout msg -> Html msg
view model =
render <|
case model.page of
Page1 -> myView1 model
Page2 -> myView2 model
1.5 year
1.5 year
SĂ©bastien Besnier
@_sebbes_
Purescript bundle size:
Â
Purescript: Hallogen: elm-similar api