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…
Reference in a new issue