Constraints
Kevin Greene
A talk kind of about Elm
What's Elm?
What's a constraint?
With Constraints

Without Constraints

With Constraints

Without Constraints

With Constraints

Without Constraints

Constraints are bad
- Can't draw what you want
- Can't drive as fast as you want
- Can't build bridges as quickly as you want
Constraints are good
- It's hard to draw on blank canvas
- Cars are surprisingly safe
- Bridges don't collapse
Constraints in JS?
!+[]+!![] == "2"Types!
JavaScript
var getInt = function(x) {
if (x)
return 1;
else
return "1";
}
// Let's blame the intern
// But we've all written thisJavaScript
var getInt = function(x,y,z) {
if (x) {
var intermediateStep = otherFunctionCall(y);
var valueWeCareAbout = intermediateStep.deeply.nested.map
return processNumbers(valueWeCareAbout,z)
}
else
return defaultProcess(y,z);
}Elm
> getInt x = if x then 1 else "0"
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
The branches of this `if` produce different types of values.
4| getInt x = if x then 1 else "0"
^^^^^^^^^^^^^^^^^^^^
The `then` branch has type:
number
But the `else` branch is:
String
Hint: These need to match so that no matter which branch we take, we always get
back the same type of value.
Fail fast
Know why
... but I really need to make an API with different return types
Colors
- Need an array of 3 numbers (RGB)
- Need a string (hex)
- Need an array of 3 numbers (HSL)
JS
- Just an array -> bugs
- Heavier data structures lose the simplicity
Elm
type Color
= RGB Int Int Int
| HSL Int Int Int
| Hex String
nameColor x =
case x of
RGB r g b ->
"blue"
Hex hexString ->
"green"
Elm (compile fail)
This `case` does not have branches for all possibilities.
10|> case x of
11|> RGB r g b ->
12|> "blue"
13|> Hex hexString ->
14|> "green"
You need to account for the following values:
Main.HSL _ _ _
Add a branch to cover this pattern!Can't use Elm?
- TypeScript is a bit more palatable
- Flow works with vanilla JS
- Write good, detailed documentation
TypeScript
interface HSL {
kind: "hsl";
h: number;
s: number;
l: number;
}
interface RGB {
kind: "rgb";
r: number;
g: number;
b: number;
}
type Color = HSL | RGB
Flow
class HSL {
h: number;
s: number;
l: number;
}
class RGB {
r: number;
g: number;
b: number;
}
type Color = HSL | RGB;
State!
State is terrible
kind of
Change is terrible
kind of
Unexpected Change is Terrible
Elm fixes that
- Initial Model
- Model is rendered by the View
- View triggers Actions
- Actions update Model
TODO
(from evancz/elm-todomvc)
Model
type alias Model =
{ entries : List Entry
, field : String
, uid : Int
, visibility : String
}
type alias Entry =
{ description : String
, completed : Bool
, editing : Bool
, id : Int
}
emptyModel : Model
emptyModel =
{ entries = []
, visibility = "All"
, field = ""
, uid = 0
}
newEntry : String -> Int -> Entry
newEntry desc id =
{ description = desc
, completed = False
, editing = False
, id = id
}
type Msg
= NoOp
| UpdateField String
| AddView
view : Model -> Html Msg
view model =
div
[ class "todomvc-wrapper"
, style [ ( "visibility", "hidden" ) ]
]
[ section
[ class "todoapp" ]
[ viewInput model.field
, viewEntries model.visibility model.entries
, viewControls model.visibility model.entries
]
, infoFooter
]
viewInput : String -> Html Msg
viewInput task =
header
[ class "header" ]
[ h1 [] [ text "todos" ]
, input
[ class "new-todo"
, placeholder "What needs to be done?"
, autofocus True
, value task
, name "newTodo"
, onInput UpdateField
, onEnter Add
]
[]
]Update
-- How we update our Model on a given Msg?
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp ->
model
Add ->
{ model
| uid = model.uid + 1
, field = ""
, entries =
if String.isEmpty model.field then
model.entries
else
model.entries ++ [ newEntry model.field model.uid ]
}
UpdateField str ->
{ model | field = str }Explicitly tie everything together
main =
Html.beginnerProgram
{ model = model
, view = view
, update = update
}Benefits
- Easy to reason about
- Every possible action in your application is documented with Msg
- Every possible structure of your data is accounted for in your model
- Every possible case is handled by your update function
Cons
- Keyboard to effect time is slower with changes
but still faster overall
Can't use Elm?
- Redux is basically Elm in JavaScript, backed by React
- Use strong conventions to show when you will change state
- Keep a central model with the current state
APIs!
Developers write bad code
entryDecoder: Decoder Entry
entryDecoder =
object3 Entry
("description" := string)
("id" := int)
("completed" := bool)
fetchData =
Http.get entryDecoder "https://todos.com"
|> Task.mapError toString
|> Task.perform ErrorOccurred DataFetched
Libraries Change
_
Elm Packages enforce SemVer
(once again, types are pretty cool)
I can't use Elm
- Validate all data from outside sources
- Practice SemVer in any packages you publish
- Make sure you use APIs that are tested (OSS libraries love testing contributions)
Style!
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
Elm
div(
List.empty,
List(
button(
List(onClick(Decrement)),
List(text("-"))
),
div(
List.empty,
List(text (model.toString))
),
button(
List(onClick(Increment)),
List(text("+"))
)
)
)Scala
I can't use Elm
- Use a linter
- Use pre-commit hooks
- Use JSX if you're using React
Style!
(the other kind)
css =
(stylesheet << namespace "flashcard")
[ body
[ minWidth (px 500)
]
, h1 [ textAlign center ]
, (.) Flashcard
[ color (hex "888888")
, height cardHeight
, width cardWidth
, textAlign center
, border3 (px 3) solid (hex "333333")
, marginLeft auto
, marginRight auto
]
, (.) FlashcardContainer
[ marginLeft auto
, marginRight auto
, width (px 500)
]
, (.) NextButton
[ margin (px 50) ]
]
div [ class [ FlashcardCss.Flashcard ] ]
[ h1 [] [ flashcardTextArea flashcard display ]
]
I can't use Elm
- Use CSS Modules
- Use CSS Linters
Constraints:
Fail Fast
Know Why
Questions?
Constraints
By Kevin Greene
Constraints
- 206