about summary refs log tree commit diff
path: root/website
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-04-11T15·50+0100
committerWilliam Carroll <wpcarro@gmail.com>2020-04-11T15·50+0100
commit3c8bfe85c9fb6dfa064cb1fc5bcc93af46cdd49f (patch)
tree7f5087f6169c51ab6fbe721af0979613015ff0ad /website
parent52eb456a0f25913c44615c6828562eb19c94393b (diff)
Prefer type alias to type
Elm reminds me of Haskell. In fact, I'm using `haskell-mode` (for now) in Emacs
to write my Elm code, and it works reliably. I'm not writing a Haskell app, but
if I were, I would define my application Model with the following Haskell code:

```haskell
data Model = Model { whitelistedChords :: [Theory.Chord]
                   , selectedChord :: Theory.Chord
                   , isPaused :: Bool
                   , tempo :: Int
                   }
```

When I first modelled my application state, I did something similar. After
reading more Elm examples of SPAs, I see that people prefer using type aliases
to define records. As far as I know, you cannot do this in Haskell; I believe
all types are "tagged" (something about "nominal typing" comes to mind). Anyhow,
Elm isn't Haskell; Haskell has cool features like type classes; Elm has cool
features like human-readable error messages and exhaustiveness checking for
cases. I love Haskell, and I love Elm, and you didn't ask.

Anyhow, this commit refactors my records as type aliases instead of types. I
think the resulting code is more readable and ergonomic.
Diffstat (limited to 'website')
-rw-r--r--website/sandbox/chord-drill-sergeant/src/Main.elm81
-rw-r--r--website/sandbox/chord-drill-sergeant/src/Theory.elm27
2 files changed, 48 insertions, 60 deletions
diff --git a/website/sandbox/chord-drill-sergeant/src/Main.elm b/website/sandbox/chord-drill-sergeant/src/Main.elm
index bfa03f629bea..3f1168b533a0 100644
--- a/website/sandbox/chord-drill-sergeant/src/Main.elm
+++ b/website/sandbox/chord-drill-sergeant/src/Main.elm
@@ -11,11 +11,12 @@ import Time exposing (..)
 import Piano
 import Theory
 
-type Model = Model { whitelistedChords : List Theory.Chord
-                   , selectedChord : Theory.Chord
-                   , isPaused : Bool
-                   , tempo : Int
-                   }
+type alias Model =
+  { whitelistedChords : List Theory.Chord
+  , selectedChord : Theory.Chord
+  , isPaused : Bool
+  , tempo : Int
+  }
 
 type Msg = NextChord
          | NewChord Theory.Chord
@@ -28,7 +29,7 @@ tempoStep : Int
 tempoStep = 100
 
 viewChord : Theory.Chord -> String
-viewChord (Theory.Chord (note, chordType, chordPosition)) =
+viewChord {note, chordType, chordPosition} =
   viewNote note ++ " " ++
   (case chordType of
      Theory.Major -> "major"
@@ -65,19 +66,23 @@ viewNote note =
     Theory.B -> "B"
 
 cmajor : Theory.Chord
-cmajor = Theory.Chord (Theory.C, Theory.Major, Theory.First)
+cmajor =
+  { note = Theory.C
+  , chordType = Theory.Major
+  , chordPosition = Theory.First
+  }
 
 {-| The initial state for the application. -}
 init : Model
 init =
-  Model { whitelistedChords = Theory.allChords
-        , selectedChord = cmajor
-        , isPaused = True
-        , tempo = 1000
-        }
+  { whitelistedChords = Theory.allChords
+  , selectedChord = cmajor
+  , isPaused = True
+  , tempo = 1000
+  }
 
 subscriptions : Model -> Sub Msg
-subscriptions (Model {isPaused, tempo}) =
+subscriptions {isPaused, tempo} =
   if isPaused then
     Sub.none
   else
@@ -85,71 +90,47 @@ subscriptions (Model {isPaused, tempo}) =
 
 {-| Now that we have state, we need a function to change the state. -}
 update : Msg -> Model -> (Model, Cmd Msg)
-update msg (Model {whitelistedChords, selectedChord, isPaused, tempo}) =
+update msg model =
   case msg of
-    NewChord chord -> ( Model { whitelistedChords = whitelistedChords
-                              , selectedChord = chord
-                              , isPaused = isPaused
-                              , tempo = tempo
-                              }
+    NewChord chord -> ( { model | selectedChord = chord }
                       , Cmd.none
                       )
-    NextChord -> ( Model { whitelistedChords = whitelistedChords
-                         , selectedChord = selectedChord
-                         , isPaused = isPaused
-                         , tempo = tempo
-                         }
+    NextChord -> ( model
                  , Random.generate (\x ->
                                       case x of
                                         (Just chord, _) -> NewChord chord
                                         (Nothing, _)    -> NewChord cmajor)
-                   (Random.List.choose whitelistedChords)
+                   (Random.List.choose model.whitelistedChords)
                  )
-    Play -> ( Model { whitelistedChords = whitelistedChords
-                    , selectedChord = selectedChord
-                    , isPaused = False
-                    , tempo = tempo
-                    }
+    Play -> ( { model | isPaused = False }
             , Cmd.none
             )
-    Pause -> ( Model { whitelistedChords = whitelistedChords
-                     , selectedChord = selectedChord
-                     , isPaused = True
-                     , tempo = tempo
-                    }
+    Pause -> ( { model | isPaused = True }
              , Cmd.none
              )
-    IncreaseTempo -> ( Model { whitelistedChords = whitelistedChords
-                             , selectedChord = selectedChord
-                             , isPaused = isPaused
-                             , tempo = tempo - tempoStep
-                             }
+    IncreaseTempo -> ( { model | tempo = model.tempo - tempoStep }
                      , Cmd.none
                      )
-    DecreaseTempo -> ( Model { whitelistedChords = whitelistedChords
-                             , selectedChord = selectedChord
-                             , isPaused = isPaused
-                             , tempo = tempo + tempoStep
-                             }
+    DecreaseTempo -> ( { model | tempo = model.tempo + tempoStep }
                      , Cmd.none
                      )
 
 playPause : Model -> Html Msg
-playPause (Model {isPaused}) =
+playPause {isPaused} =
   if isPaused then
     button [ onClick Play ] [ text "Play" ]
   else
     button [ onClick Pause ] [ text "Pause" ]
 
 view : Model -> Html Msg
-view (Model {selectedChord, tempo} as model) =
-  div [] [ p [] [ text (viewChord selectedChord) ]
-         , p [] [ text (String.fromInt tempo) ]
+view model =
+  div [] [ p [] [ text (viewChord model.selectedChord) ]
+         , p [] [ text (String.fromInt model.tempo) ]
          , button [ onClick NextChord ] [ text "Next Chord" ]
          , button [ onClick IncreaseTempo ] [ text "Faster" ]
          , button [ onClick DecreaseTempo ] [ text "Slower" ]
          , playPause model
-         , Piano.render { highlight = Theory.notesForChord selectedChord }
+         , Piano.render { highlight = Theory.notesForChord model.selectedChord }
          ]
 
 {-| For now, I'm just dumping things onto the page to sketch ideas. -}
diff --git a/website/sandbox/chord-drill-sergeant/src/Theory.elm b/website/sandbox/chord-drill-sergeant/src/Theory.elm
index c80fffc39f3c..4a275fa79e1f 100644
--- a/website/sandbox/chord-drill-sergeant/src/Theory.elm
+++ b/website/sandbox/chord-drill-sergeant/src/Theory.elm
@@ -36,7 +36,11 @@ type Interval = Half
               | MinorThird
 
 {-| A bundle of notes which are usually, but not necessarily harmonious. -}
-type Chord = Chord (Note, ChordType, ChordPosition)
+type alias Chord =
+  { note : Note
+  , chordType : ChordType
+  , chordPosition : ChordPosition
+  }
 
 {-| Many possible chords exist. This type encodes the possibilities. I am
 tempted to model these in a more "DRY" way, but I worry that this abstraction
@@ -62,7 +66,10 @@ type ChordPosition = First
 
 {-| Songs are written in one or more keys, which define the notes and therefore
 chords that harmonize with one another. -}
-type Key = Key (Note, Mode)
+type alias Key =
+  { note : Note
+  , mode : Mode
+  }
 
 {-| We create "scales" by enumerating the notes of a given key. These keys are
 defined by the "tonic" note and the "mode".  I thought about including Ionian,
@@ -160,16 +167,13 @@ applySteps steps note =
 
 {-| Return a list of the notes that comprise a `chord` -}
 notesForChord : Chord -> List Note
-notesForChord chord =
-  case chord of
-    -- TODO(wpcarro): Use the Position to rotate the chord n times
-    Chord (note, chordType, _) -> note :: applySteps (intervalsForChordType chordType) note
+notesForChord {note, chordType} =
+  note :: applySteps (intervalsForChordType chordType) note
 
 {-| Return the scale for a given `key` -}
 notesForKey : Key -> List Note
-notesForKey key =
-  case key of
-    Key (note, mode) -> applySteps (intervalsForMode mode) note
+notesForKey {note, mode} =
+  applySteps (intervalsForMode mode) note
 
 {-| Return a list of all of the chords that we know about. -}
 allChords : List Chord
@@ -206,4 +210,7 @@ allChords =
     notes
     |> List.Extra.andThen (\note -> chordTypes
     |> List.Extra.andThen (\chordType -> chordPositions
-    |> List.Extra.andThen (\chordPosition -> [Chord (note, chordType, chordPosition)])))
+    |> List.Extra.andThen (\chordPosition -> [{ note = note
+                                             , chordType = chordType
+                                             , chordPosition = chordPosition
+                                             }])))