Learning Elm + Elm UI


#1

[EDIT]
I’ve finally set up a Github account for easier code sharing :smiley:


[/EDIT]

As suggested here I’m opening a new thread dedicated to my Elm-UI pains :slight_smile:
Seems like I’m finally getting the hang of the ElmUI documentation … more or less.

One question that has arisen for me is… what TYPE are the functions I am writing? o_O
I would like to have a nice Type Declaration for every function, but I am not sure what is what.

I’ve been using the compiler-help to reverse-engineer the correct type declaration of all my current functions, except for these two…

view form =
    layout [] <|
        row [ centerY, centerX, spacing 10 ]
(other stuff)

the annotation should be?
view : Form -> ???

and also this one…

viewValidation model =
    let
        ( color, message ) =
            if model.validity then
                if isEmpty model.name then
                    ( red, "Please input a valid username!" )

                else if isEmpty model.age || all isDigit model.age == False then
                    ( red, "Please input a valid age!" )

                else if length model.password < 8 then
                    ( red, "Password must be at least 8 characters!" )

                else if any isDigit model.password == False then
                    ( red, "Password must contain at least 1 number!" )

                else if any isUpper model.password == False then
                    ( red, "Password must contain at least 1 capital character!" )

                else if any isLower model.password == False then
                    ( red, "Password must contain at least 1 lower character!" )

                else if model.password /= model.passwordAgain then
                    ( red, "Passwords do not match!" )

                else
                    ( green, "All data checks out :)" )

            else
                ( white, "empty filler" )
    in
    el [ Font.color color ] (paragraph [] [ text message ])

Elm Guide stumbling blocks
#2

A hacky thing you can do is write the type of view as

view : Form -> Int 

and look at the compiler error. It will tell you which type the compiler expects.

You can also look at the type of your body. In the first example, you’re using Layout.layout, which has type layout : List (Attribute msg) -> Element msg -> Html msg see docs, meaning that the type of view needs to be view : Form -> Html msg (or Html Msg if you use a concrete message type in there somewhere)

Similar for the second one, it ends in el, which has type el : List (Attribute msg) -> Element msg -> Element msg docs, so it needs to be viewValidation : Model -> Element msg.

Hope that helps. As an aside, the elm slack is also a great place to ask this kind of questions, and it is easier to have quick feedback/interactions there.


#4

In your view function, since Element.layout is your final function call, the type of view will be whatever arguments view takes ( Form ) and whatever portion of Element.layouts's type hasn’t been “filled in”. For example, Element.layout's (https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element#layout) type is layout : List (Attribute msg) -> Element msg -> Html msg. So if you are passing it a list of attributes, and an element, then you can cross out those two types, as they are “filled in” and all you have remaining is Html msg. Putting this all together you have

--  view's argument   The remainder of Element.layout's type
--       |           |
view : Form -> Html msg
view form = 
    ...

Same thing with viewValidation.
Element.el's type is el : List (Attribute msg) -> Element msg -> Element msg

And since you are filling in both of it’s paramaters, you can cross them out and you’re left with Element msg. Then the type signature of viewValidation becomes viewValidation : Model -> Element msg

Now one tricky detail is that these types live in other modules, they are not automatically exposed to your module, so you either need to reference them by their full path, Html.Html and Element.Element or expose them on your import line.

import Html exposing (Html)
import Element exposing (Element)

Hope that helps!


#5

@folkertdev thanks for the reply :slight_smile:

I’m using the forum rather than the Slack because for me it’s a more permanent and easier to access record… I might need these answers at a later date, and this is more findable/searchable. Also, if I post here I create a context for anyone trying to reply… while in an instant chat I need to re-explain my circumstances every time. Also, personal taste, instant chats feel crowded and chaotic to me; unless I really need a quick answer, I prefer to stick with a good old forum :slight_smile:

Regarding the type investigation ( :male_detective: ) I did try using the compiler to find the expected type, but in the aforementioned two cases the result was confusing.

You are right on viewValidation :slight_smile:
I was misled by how the compiler threw the error in different places in my code.

Instead the view function still gives me problems…

The body is:

    #Html.Html Msg#

But the type annotation on `view` says it should be:

    #Int#

So I tried writing view : Form -> Html.Html Msg but this gives me another error…

NAMING ERROR - I cannot find a `Html.Html` type:

84| view : Form -> Html.Html Msg
                   #^^^^^^^^^#
I cannot find a `Html` import. 

It can’t find it because I did not import the Html module, as ElmUI doesn’t need it.
Should I import a module just for the sake of the type annotation? Does it make sense? Isn’t it a waste of resources?
Or will I later need the Html module anyway when I go from elm-reactor to the actual web?


#6

Crossposting! :stuck_out_tongue:

Yes, that helps! Thanks for the explanation of what goes into the Type Annotation… I thought I needed to somehow represent what went on in the whole function :stuck_out_tongue_closed_eyes:

Instead it is just the things I “feed” the function from outside of it (its arguments) and the final end result.
All the things in between are not part of the type declaration.
Correct?

Still, my questions about the Html module stand.

EDIT:
to be clear, this is my declaration…

module Main exposing (Form, Msg(..), init, main, update, view)

import Browser

import Char exposing (..)

import Element exposing (..)

import Element.Background as Background

import Element.Border as Border

import Element.Events as Events

import Element.Font as Font

import Element.Input as Input

import String exposing (..)

#7

If you want to be specific then you do need to import Html. This is not a waste because

  • elm-ui already uses Html, so it is not really an extra dependency
  • you only use types from that module. Types get removed (elided) at compile time. With 0.19, even if you use some function from Html, only that function is in the output JS (all functions you don’t used are not in the compiler output)
  • it is quite likely you’ll need to use Html directly, for some css property that isn’t supported or to wrap some SVG icon.

On the other hand, you could also use

view : Model -> Element msg
view  _ = ... 
main = 
    { view = \model -> Layout.layout [] (view model)
     ...
    } 

You only convert to html at the last moment. I quite like this for applications that use elm-ui; it is clear from the types that it does.


#8
view : Model -> Element msg
view  _ = ... 
main = 
    { view = \model -> Layout.layout [] (view model)
     ...
    } 

That is brilliant. Thanks for that!


#9

mmm … I still have problems.

1]
I can’t find (and the compiler does not recognize) the Layout.layout type.
I only know of the Element.layout type, with this signature…
layout : List (Attribute msg) -> Element msg -> Html msg
Is this the same thing?

2]
What happens in the variation suggested by @folkertdev ?
If I understand it correctly… I don’t set main view to be the view function, but instead set main view to be an anonymous function that takes the model (form, in my case) and returns an Element.layout loaded with the view function and its argument (again, form).

What does this change do?
Also… even with it I still need to import the Html module, right?


#10

current status

module Main exposing (Form, Msg(..), init, main, update, view)

import Browser
import Char exposing (..)
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Events as Events
import Element.Font as Font
import Element.Input as Input
import Html as Html exposing (..)
import String exposing (..)
main : Program () Form Msg
main =
    Browser.sandbox
        { init = init
        , update = update
        , view = \form -> Element.layout [] (view form)
        }
view : Form -> Element msg
view form =
layout [] <| etc...

Right now the compiler throws this error:

TYPE MISMATCH - Something is off with the body of the `view` definition:

[the whole view function body]

The body is:
    #Html Msg#

But the type annotation on `view` says it should be:
    #Element msg#

I feel like I’m stuck in a loop.
If I change one thing the error is “you are Element but should be Html”.
If I change another thing the error is “you are Html but should be Element”.

I’m stuck :confused:


#11

You should not use the layout function in your view.
The layout function is only used If you need to convert An Elm-UI function to Html…
(Element msg to Html msg)

In the happy world of elm-ui, all your function return Element msg, so no need for the layout function.


#12

So… how do I use it? o_O
How am I supposed to build the page view correctly?
From the documentations it is mightily unclear, thus I followed the example given by MdGriffith here:


#13

instead of

view : Form -> Element msg
view form =
    layout [] <| etc...

us only

view : Form -> Element msg
view form =
    etc...

In your current example, layout is applied twice (once in view, and then again in the definition of main.
You should only use it once.


#14

The main function returns an Html msg. The Element.layout function takes an Element msg and returns an Html msg. So you can define your view function strictly in terms of the elm-ui structures, but then you have to send the output of that view function to Element.layout in the main function in order to get the Html msg that main expects. You are correct that Layout.layout is an error that should be Element.layout.

Think of it like this:

view : A -> B -> C

versus

view: A -> B
...
main = { ...
              view = (\model -> BtoC (view A)
             ...
           }

where BtoC is the stand in for Element.layout.


#15

Elm-Ui elements like row,column,el and text does not produce Html type, they produce the Element type.
Elm on the other hand expects that your view function produces a view of Html type.
So at the top level you will need a conversion from Element type to Html type, that is the purpose of the Element.layout function. It is not used again, just on the top level.
I put together an example:

module Main exposing (..)
import Element exposing (..)
import Html exposing(Html)


model =
    {names = ["Steve","Mona","Lisa"] }
    
    
main : Html msg
main = elmUiTopView model.names


elmUiTopView : List String -> Html msg   
elmUiTopView names =
    Element.layout 
        []
        (allOtherElements names)
    
allOtherElements : List String -> Element msg     
allOtherElements names =
    Element.column 
        [ Element.spacing 10 ] 
        ( List.map myNameViewFunction names)
        
        
myNameViewFunction : String -> Element msg     
myNameViewFunction name =
    Element.text name

#16

Answer to your initial question:
All the functions you create produces the type: Element msg
If you have any events then they produce Element Msg

I guess if the elm-ui examples had type declerations, much of your pains would have been avoided ? :slight_smile:


#17

Thanks everyone for the help! :smiling_face_with_three_hearts:
Now I (think that I) understand better what Element.layout actually does.

So I removed layout from the view function, which allowed me to write a Type Annotation that only returns Element Msg.

Then I kept layout in the main function, in the view definition, as an anonymous function like suggested by @folkertdev .
If I understand correctly… the view expects html stuff… so the anonymous function takes the model and plugs it into a layer function that in turn takes the Element-y view and crunches it into an Html-y equivalent… so the main view is happy about its type.
Right?

This actually allows me to remove the Html Import. I understand it’s not problematic, but I prefer not having stuff in the code I am not actively using.

It’s all updated in the Github repo :slight_smile:

@Atlewee yes, a bit more informative package documentation would be nice.
I mean… it’s already clean and succinct, and here and there the author took the time to spend a few extra words (which help immensely :heart: ).
But I feel like the info the author of a package deems useful is not the same as the info a total n00b like me deems useful.
Most times I just struggle to understand HOW to read/use any given function and element described.
Even knowing the theory, it takes time and practice to digest stuff like:

isUpper : Char -> Bool … which is pretty clear

paragraph : List (Attribute msg) -> List (Element msg) -> Element msg … which is already more complex and abstract

I have the feeling that a tutorial spending a bit of time specifically to help n00bs get familiar with how to read and use the Elm Docs would help lots of curious people. Maybe :wink:


#18

Continuing on the Elm Guide path I got to the Random chapter.
The basic exercise worked fine.
I also figured out how to display images in place of the bare numbers.

But now I’m stuck on the SVG part.

The SVG module produces Html types… but my view function expects Element types (which are converted to Html through the layout function in the main function).
I’m not understanding how I could plug an SVG thing within my Elelement structure :stuck_out_tongue:

Help? :slight_smile:

Updated Git :


#19

At end of https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/Element there is html : Html msg -> Element msg which seems to be for this use case.

(I havn’t tried actually using this yet.)


#20

Using that was quite easy: In your view change svgDie to html svgDie or perhaps Element.html svgDie as you have quite many exposed things.


#21

Yep, it works! Thanks :smiley: