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.
			
			
This commit is contained in:
		
							parent
							
								
									52eb456a0f
								
							
						
					
					
						commit
						3c8bfe85c9
					
				
					 2 changed files with 48 additions and 60 deletions
				
			
		|  | @ -11,7 +11,8 @@ import Time exposing (..) | |||
| import Piano | ||||
| import Theory | ||||
| 
 | ||||
| type Model = Model { whitelistedChords : List Theory.Chord | ||||
| type alias Model = | ||||
|   { whitelistedChords : List Theory.Chord | ||||
|   , selectedChord : Theory.Chord | ||||
|   , isPaused : Bool | ||||
|   , tempo : Int | ||||
|  | @ -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 | ||||
|   { 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. -} | ||||
|  |  | |||
|  | @ -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 | ||||
|                                              }]))) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue