'How do I add dynamic content in the HTML part of my Elm file without getting a "Type mismatch error"?

I have an Elm file with some HTML content in a function called view.

Part of the HTML content is dynamically generated via a map function that extracts data from a record.

Below is the code from that file.

module PhotoGroove exposing (main)
import Html exposing (div,h1,img,text)
import Html.Attributes exposing (..)

--model
initialModel : { photos : List { url : String }, selectedPhoto : String }
initialModel =
    { photos = 
        [ { url = "1.jpeg" }
        , { url = "2.jpeg" }
        , { url = "3.jpeg" }
        ]
    , selectedPhoto = "1.jpeg"
    }

--view
urlPrefix : String
urlPrefix = "http://elm-in-action.com/"
viewThumbnail : String -> { a | url : String } -> Html.Html msg
viewThumbnail selectedPhoto thumb = 
    if selectedPhoto == thumb.url
        then img [src (urlPrefix ++ thumb.url), class "selected"] []
        else img [src (urlPrefix ++ thumb.url)] []

view : List { a | url : String } -> Html.Html msg
view model = 
    div [class "context"]
        [h1 [] [text "Photo Groove"]
        ,div [id "tumbnail"] (List.map viewThumbnail model) -- error message on "model"
        ]

--main
main = view initialModel -- error message on "initialModel"

However, I got error messages on the words "model" and "initialModel". Below is the error message for "model":

TYPE MISMATCH - The 2nd argument to `div` is not what I expect:
29|         ,div [id "tumbnail"] (List.map viewThumbnail model)
                                  #^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
This `map` call produces:
    List #({ a | url : String } -> Html.Html msg)#
But `div` needs the 2nd argument to be:
    List #(Html.Html msg)#
#Hint#: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!

And for initialModel:

TYPE MISMATCH - The 1st argument to `view` is not what I expect:
35| main = view initialModel
                #^^^^^^^^^^^^#
This `initialModel` value is a:
    #{ photos : List { url : String }, selectedPhoto : String }#
But `view` needs the 1st argument to be:
    #List { a | url : String }#

I got that I need to somehow convert the objects directly into HTML msg types, but I don't know how.

I even tried to change the type for view, but that did not work either.

How do I fix this problem?



Solution 1:[1]

This is a great example of Elm's type system (or a good static type system in general) helping you figure out logical errors in your code.

The problems here are all due to your view function, and in particular with faulty assumptions it makes about what is in your model.

You are trying to map a function over the model argument, which assumes model is a list - but it isn't, it's a record containing both a list of all photos and a string identifying the selected photo. That makes logical sense as a model to use in this situation, so rather than changing the model type (which would otherwise be a sensible option) I would suggest changing view so that it works with your actual model type.

Another problem, which is highlighted in the first of the 2 error messages you quote, is that viewThumbnail can't be mapped over a list of photos anyway because it actually takes 2 arguments, the first of which isn't a list. You do want to use this function to produce the list of images in view's output - but you also need to provide it with the selectedPhoto, which of course comes from the other field in your model that you've ignored so far.

So the only changes needed to your original code to get this to compile are:

  1. change the type signature of view to actually match your model's type. (This is what the second of the two error messages is about). It should be view : { photos : List { url : String }, selectedPhoto : String } -> Html.Html msg (You probably want to make a type alias for this model type, at least if you expand this app to any much greater size.)

  2. inside the function body,

change this

List.map viewThumbnail model

to

List.map (\photo -> viewThumbnail model.selectedPhoto photo) model.photos

Hopefully you can see what the above is doing - the \ is for an anonymous function, that takes one of the photos and calls viewThumbnail with that as second argument and model.selectedPhoto always as the first argument.

And note finally that you can simplify the above by taking advantage of Elm's built-in function currying, to just

List.map (viewThumbnail model.selectedPhoto) model.photos

which I and probably most others familiar with functional programming find much cleaner and easy to read.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1