Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is let-in lazy in its bindings in Elm?

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?

like image 352
Björn Norgren Avatar asked Oct 09 '18 21:10

Björn Norgren


1 Answers

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.

like image 101
Chad Gilbert Avatar answered Sep 18 '22 00:09

Chad Gilbert