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,11 +11,12 @@ import Time exposing (..) | ||||||
| import Piano | import Piano | ||||||
| import Theory | import Theory | ||||||
| 
 | 
 | ||||||
| type Model = Model { whitelistedChords : List Theory.Chord | type alias Model = | ||||||
|                    , selectedChord : Theory.Chord |   { whitelistedChords : List Theory.Chord | ||||||
|                    , isPaused : Bool |   , selectedChord : Theory.Chord | ||||||
|                    , tempo : Int |   , isPaused : Bool | ||||||
|                    } |   , tempo : Int | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
| type Msg = NextChord | type Msg = NextChord | ||||||
|          | NewChord Theory.Chord |          | NewChord Theory.Chord | ||||||
|  | @ -28,7 +29,7 @@ tempoStep : Int | ||||||
| tempoStep = 100 | tempoStep = 100 | ||||||
| 
 | 
 | ||||||
| viewChord : Theory.Chord -> String | viewChord : Theory.Chord -> String | ||||||
| viewChord (Theory.Chord (note, chordType, chordPosition)) = | viewChord {note, chordType, chordPosition} = | ||||||
|   viewNote note ++ " " ++ |   viewNote note ++ " " ++ | ||||||
|   (case chordType of |   (case chordType of | ||||||
|      Theory.Major -> "major" |      Theory.Major -> "major" | ||||||
|  | @ -65,19 +66,23 @@ viewNote note = | ||||||
|     Theory.B -> "B" |     Theory.B -> "B" | ||||||
| 
 | 
 | ||||||
| cmajor : Theory.Chord | 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. -} | {-| The initial state for the application. -} | ||||||
| init : Model | init : Model | ||||||
| init = | init = | ||||||
|   Model { whitelistedChords = Theory.allChords |   { whitelistedChords = Theory.allChords | ||||||
|         , selectedChord = cmajor |   , selectedChord = cmajor | ||||||
|         , isPaused = True |   , isPaused = True | ||||||
|         , tempo = 1000 |   , tempo = 1000 | ||||||
|         } |   } | ||||||
| 
 | 
 | ||||||
| subscriptions : Model -> Sub Msg | subscriptions : Model -> Sub Msg | ||||||
| subscriptions (Model {isPaused, tempo}) = | subscriptions {isPaused, tempo} = | ||||||
|   if isPaused then |   if isPaused then | ||||||
|     Sub.none |     Sub.none | ||||||
|   else |   else | ||||||
|  | @ -85,71 +90,47 @@ subscriptions (Model {isPaused, tempo}) = | ||||||
| 
 | 
 | ||||||
| {-| Now that we have state, we need a function to change the state. -} | {-| Now that we have state, we need a function to change the state. -} | ||||||
| update : Msg -> Model -> (Model, Cmd Msg) | update : Msg -> Model -> (Model, Cmd Msg) | ||||||
| update msg (Model {whitelistedChords, selectedChord, isPaused, tempo}) = | update msg model = | ||||||
|   case msg of |   case msg of | ||||||
|     NewChord chord -> ( Model { whitelistedChords = whitelistedChords |     NewChord chord -> ( { model | selectedChord = chord } | ||||||
|                               , selectedChord = chord |  | ||||||
|                               , isPaused = isPaused |  | ||||||
|                               , tempo = tempo |  | ||||||
|                               } |  | ||||||
|                       , Cmd.none |                       , Cmd.none | ||||||
|                       ) |                       ) | ||||||
|     NextChord -> ( Model { whitelistedChords = whitelistedChords |     NextChord -> ( model | ||||||
|                          , selectedChord = selectedChord |  | ||||||
|                          , isPaused = isPaused |  | ||||||
|                          , tempo = tempo |  | ||||||
|                          } |  | ||||||
|                  , Random.generate (\x -> |                  , Random.generate (\x -> | ||||||
|                                       case x of |                                       case x of | ||||||
|                                         (Just chord, _) -> NewChord chord |                                         (Just chord, _) -> NewChord chord | ||||||
|                                         (Nothing, _)    -> NewChord cmajor) |                                         (Nothing, _)    -> NewChord cmajor) | ||||||
|                    (Random.List.choose whitelistedChords) |                    (Random.List.choose model.whitelistedChords) | ||||||
|                  ) |                  ) | ||||||
|     Play -> ( Model { whitelistedChords = whitelistedChords |     Play -> ( { model | isPaused = False } | ||||||
|                     , selectedChord = selectedChord |  | ||||||
|                     , isPaused = False |  | ||||||
|                     , tempo = tempo |  | ||||||
|                     } |  | ||||||
|             , Cmd.none |             , Cmd.none | ||||||
|             ) |             ) | ||||||
|     Pause -> ( Model { whitelistedChords = whitelistedChords |     Pause -> ( { model | isPaused = True } | ||||||
|                      , selectedChord = selectedChord |  | ||||||
|                      , isPaused = True |  | ||||||
|                      , tempo = tempo |  | ||||||
|                     } |  | ||||||
|              , Cmd.none |              , Cmd.none | ||||||
|              ) |              ) | ||||||
|     IncreaseTempo -> ( Model { whitelistedChords = whitelistedChords |     IncreaseTempo -> ( { model | tempo = model.tempo - tempoStep } | ||||||
|                              , selectedChord = selectedChord |  | ||||||
|                              , isPaused = isPaused |  | ||||||
|                              , tempo = tempo - tempoStep |  | ||||||
|                              } |  | ||||||
|                      , Cmd.none |                      , Cmd.none | ||||||
|                      ) |                      ) | ||||||
|     DecreaseTempo -> ( Model { whitelistedChords = whitelistedChords |     DecreaseTempo -> ( { model | tempo = model.tempo + tempoStep } | ||||||
|                              , selectedChord = selectedChord |  | ||||||
|                              , isPaused = isPaused |  | ||||||
|                              , tempo = tempo + tempoStep |  | ||||||
|                              } |  | ||||||
|                      , Cmd.none |                      , Cmd.none | ||||||
|                      ) |                      ) | ||||||
| 
 | 
 | ||||||
| playPause : Model -> Html Msg | playPause : Model -> Html Msg | ||||||
| playPause (Model {isPaused}) = | playPause {isPaused} = | ||||||
|   if isPaused then |   if isPaused then | ||||||
|     button [ onClick Play ] [ text "Play" ] |     button [ onClick Play ] [ text "Play" ] | ||||||
|   else |   else | ||||||
|     button [ onClick Pause ] [ text "Pause" ] |     button [ onClick Pause ] [ text "Pause" ] | ||||||
| 
 | 
 | ||||||
| view : Model -> Html Msg | view : Model -> Html Msg | ||||||
| view (Model {selectedChord, tempo} as model) = | view model = | ||||||
|   div [] [ p [] [ text (viewChord selectedChord) ] |   div [] [ p [] [ text (viewChord model.selectedChord) ] | ||||||
|          , p [] [ text (String.fromInt tempo) ] |          , p [] [ text (String.fromInt model.tempo) ] | ||||||
|          , button [ onClick NextChord ] [ text "Next Chord" ] |          , button [ onClick NextChord ] [ text "Next Chord" ] | ||||||
|          , button [ onClick IncreaseTempo ] [ text "Faster" ] |          , button [ onClick IncreaseTempo ] [ text "Faster" ] | ||||||
|          , button [ onClick DecreaseTempo ] [ text "Slower" ] |          , button [ onClick DecreaseTempo ] [ text "Slower" ] | ||||||
|          , playPause model |          , 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. -} | {-| For now, I'm just dumping things onto the page to sketch ideas. -} | ||||||
|  |  | ||||||
|  | @ -36,7 +36,11 @@ type Interval = Half | ||||||
|               | MinorThird |               | MinorThird | ||||||
| 
 | 
 | ||||||
| {-| A bundle of notes which are usually, but not necessarily harmonious. -} | {-| 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 | {-| 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 | 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 | {-| Songs are written in one or more keys, which define the notes and therefore | ||||||
| chords that harmonize with one another. -} | 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 | {-| 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, | 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` -} | {-| Return a list of the notes that comprise a `chord` -} | ||||||
| notesForChord : Chord -> List Note | notesForChord : Chord -> List Note | ||||||
| notesForChord chord = | notesForChord {note, chordType} = | ||||||
|   case chord of |   note :: applySteps (intervalsForChordType chordType) note | ||||||
|     -- TODO(wpcarro): Use the Position to rotate the chord n times |  | ||||||
|     Chord (note, chordType, _) -> note :: applySteps (intervalsForChordType chordType) note |  | ||||||
| 
 | 
 | ||||||
| {-| Return the scale for a given `key` -} | {-| Return the scale for a given `key` -} | ||||||
| notesForKey : Key -> List Note | notesForKey : Key -> List Note | ||||||
| notesForKey key = | notesForKey {note, mode} = | ||||||
|   case key of |   applySteps (intervalsForMode mode) note | ||||||
|     Key (note, mode) -> applySteps (intervalsForMode mode) note |  | ||||||
| 
 | 
 | ||||||
| {-| Return a list of all of the chords that we know about. -} | {-| Return a list of all of the chords that we know about. -} | ||||||
| allChords : List Chord | allChords : List Chord | ||||||
|  | @ -206,4 +210,7 @@ allChords = | ||||||
|     notes |     notes | ||||||
|     |> List.Extra.andThen (\note -> chordTypes |     |> List.Extra.andThen (\note -> chordTypes | ||||||
|     |> List.Extra.andThen (\chordType -> chordPositions |     |> 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