-
-
Save dennisreimann/fe2cc20ea7cb6ea60483c1d758b2d891 to your computer and use it in GitHub Desktop.
| {- | |
| This file is a reaction to the article "A small dive into, and rejection of, Elm": | |
| https://hackernoon.com/a-small-dive-into-and-rejection-of-elm-8217fd5da235#.9w3ml4r6b | |
| I think constructive criticism is very welcome in the Elm community. Pointing out | |
| possibilities for documentation improvements, mentioning missing features or ways | |
| in which the language could evolve etc. are imho a vital part of a good community. | |
| However, the article above is neither well informed nor constructive. IMHO ranting | |
| about something one has not quite understood yet is helping no one, neither the author | |
| nor us as a community. The only and worst thing that might happen is that people | |
| interested in the language might get turned off, because someone else didn't get it | |
| at first glance and therefore rejected it. That's why I took the time to show a better | |
| and easier solution to the described problem. | |
| Again: I am not saying this is perfect and I welcome everyone to reply to this and | |
| give constructive feedback. I am new to Elm myself and coming up with this was a | |
| nice way to dig a little deeper :) | |
| -} | |
| module Main exposing (..) | |
| import Html exposing (Html, div, input, form, option, select, text) | |
| import Html.Events exposing (onInput) | |
| import Html.Attributes exposing (class, id, selected, value) | |
| import Html.App | |
| type alias Model = | |
| { timeRange : TimeRange } | |
| type Msg | |
| = ChangeTimeRange String | |
| type TimeRange | |
| = AllTime | |
| | OneWeek | |
| | OneDay | |
| {- | |
| One of the main concerns in the article is that union types cannot be iterated. | |
| In a simple case like this where the union type consists of simple values this | |
| might seem like the language is lacking a nice feature. However, a union type | |
| can be an amalgamation of different types, which is oftentimes the reason for | |
| using union types. If you want to learn more about this, see my detailed article: | |
| https://dennisreimann.de/articles/elm-data-structures-union-type.html | |
| I'm far to new too the language and algebraic data types to know whether or not | |
| iterating a complex union type would be possible or a good idea, but in this | |
| case we can solve this much simpler: By wrapping the time ranges in a list with | |
| tuples containing the range and its title, we can get rid of the otherwise mentioned | |
| function timeRangeDisplayName. Of course this is kind of redundant, but you would | |
| have this redundancy either way if you want to map the ranges to a display name. | |
| -} | |
| type alias TimeRangeOption = | |
| ( TimeRange, String ) | |
| timeRangeDefaultOption : TimeRangeOption | |
| timeRangeDefaultOption = | |
| ( AllTime, "All time" ) | |
| timeRangeOptions : List TimeRangeOption | |
| timeRangeOptions = | |
| [ timeRangeDefaultOption | |
| , ( OneWeek, "One week" ) | |
| , ( OneDay, "24h" ) | |
| ] | |
| -- Helper functions for mapping the union type value to a string and back. | |
| -- This isn't the prettiest part indeed. | |
| timeRangeToId : TimeRange -> String | |
| timeRangeToId timeRange = | |
| (toString timeRange) | |
| timeRangeForId : String -> TimeRange | |
| timeRangeForId rangeId = | |
| List.filter (\n -> (toString (fst n)) == rangeId) timeRangeOptions | |
| |> List.head | |
| |> Maybe.withDefault timeRangeDefaultOption | |
| |> fst | |
| -- MODEL | |
| initModel : Model | |
| initModel = | |
| { timeRange = OneWeek | |
| } | |
| -- UPDATE | |
| update : Msg -> Model -> Model | |
| update msg model = | |
| case msg of | |
| ChangeTimeRange rangeId -> | |
| let | |
| updatedModel = | |
| { model | timeRange = (timeRangeForId rangeId) } | |
| in | |
| updatedModel | |
| -- VIEW | |
| -- separate the rendering of a single option to a function so we can | |
| -- map our timeRangeOptions and make this a little cleaner | |
| timeRangeOption : TimeRange -> ( TimeRange, String ) -> Html.Html msg | |
| timeRangeOption selectedRange timeRange = | |
| let | |
| optionValue = | |
| (fst timeRange |> timeRangeToId) | |
| optionTitle = | |
| (snd timeRange) | |
| isSelected = | |
| selectedRange == (fst timeRange) | |
| in | |
| option | |
| [ value optionValue, selected isSelected ] | |
| [ text optionTitle ] | |
| -- use onInput to handle changes of the select field. It's fine and we do not need | |
| -- to fiddle with a custom onSelect | |
| view : Model -> Html Msg | |
| view model = | |
| let | |
| options = | |
| (List.map (timeRangeOption model.timeRange) timeRangeOptions) | |
| in | |
| div [ class "wrapper" ] | |
| [ select [ id "time_range", onInput ChangeTimeRange ] options | |
| , text ("Selected time range id: " ++ (toString model.timeRange)) | |
| ] | |
| -- MAIN | |
| main : Program Never | |
| main = | |
| Html.App.beginnerProgram | |
| { model = initModel | |
| , view = view | |
| , update = update | |
| } |
I did something similar: https://gist.github.com/baritonehands/d749f2374f90298c106fda8686204a1a
Main differences: I used a Dict to lookup the values rather than filter a list. Also, I convert back to the Union type (or any type other than String).
@dennisreimann, I went through the article in "hackernoon.com" and found it quite irrelevant and your above example is really great way to prove its irrelevance.
I was thinking of another solution where we can exclude type TimeRange (and with that, we can also exclude functions such as timeRangeToId and timeRangeForId ) and directly deal with Option Msg rangeId in update function . I have created a working code for the same https://gist.github.com/arvindershinh/9a7596466b8891e6d7650c7c434e0eca .
I will highly appreciate if you and others can provide your feedback on this approach.
Specially i would like to know if it is a good design or not.
@emaniacs Thanks for pointing this out, I corrected it :)