I have two features A and B that can either be both disabled, A enabled, B enabled, but not both enabled at the same time. After watching Making Impossible States Impossible I wanted to try enforce this on the type level.
A simplified version of the solution I am considering is the following.
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type Model
= NoneEnabled
| AEnabled
| BEnabled
init : Model
init = NoneEnabled
type Msg
= EnableA
| DisableA
| EnableB
| DisableB
view : Model -> Html Msg
view model =
let -- Buttons to enable and disable features
buttons =
div [] [ button [onClick EnableA] [text "Enable A"]
, button [onClick DisableA] [text "Disable A"]
, button [onClick EnableB] [text "Enable B"]
, button [onClick DisableB] [text "Disable B"]
]
-- All possible feature states
aEnabled = div [] [text "A enabled"]
aDisabled = div [] [text "A disabled"]
bEnabled = div [] [text "B enabled"]
bDisabled = div [] [text "B disabled"]
in case model of
NoneEnabled ->
div [] [buttons, aDisabled, bDisabled]
AEnabled ->
div [] [buttons, aEnabled, bDisabled]
BEnabled ->
div [] [buttons, aDisabled, bEnabled]
update : Msg -> Model -> Model
update msg model =
case (msg, model) of
(EnableA, _) ->
AEnabled
(EnableB, _) ->
BEnabled
(DisableA, AEnabled) ->
NoneEnabled
(DisableB, BEnabled) ->
NoneEnabled
_ ->
model
main : Program () Model Msg
main =
Browser.sandbox { init = init, update = update, view = view }
My aEnabled
, aDisabled
, bEnabled
, and bDisabled
features in view
are potentially expensive to compute. Will they be evaluated regardless of which branch case model of
takes or can I rely on only the used features being evaluated?
Or phrased with a shorter example.
f c x =
let a = x + 1
b = x + 2
in case c of
True ->
a
False ->
b
Will f True 0
force the evaluation of b
in the let expression?
Elm's let
/in
statements are not lazily evaluated. You can put some Debug.log
statements to prove out the point:
f c x =
let a = Debug.log "a calculated" <| x + 1
b = Debug.log "b calculated" <| x + 2
in case c of
True ->
a
False ->
b
Calling f
just once will log both messages to the console, regardless of the input. Example here.
One way to get around this hurdle is to require an arbitrary parameter for a
and b
, such as Unit ()
:
f c x =
let a () = Debug.log "a calculated" <| x + 1
b () = Debug.log "b calculated" <| x + 2
in case c of
True ->
a ()
False ->
b ()
This variation will only evaluate the function a
or b
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With