This `div` call produces: Html (String -> Msg) But the type annotation on `view` says it should be: Html Msg - elm

I'm currently learning elm, I just stumbled on this problem where the div returns a Html (String -> Msg) instead of Html Msg.
error message I'm receiving
This div call produces:
Html (String -> Msg)
But the type annotation on view says it should be:
Html Msg
type alias Model =
{
firstNum: String,
secondNum: String,
answer: String
}
init: Model
init = { firstNum = "",
secondNum = "",
answer = ""}
type Msg =
Add String| Minus String
update: Msg -> Model -> Model
update msg model =
case msg of
Add x -> { model | answer = x}
Minus y -> { model | answer = y}
view : Model -> Html Msg
view model =
div []
[
input [ placeholder "Text to reverse", value model.firstNum] [],
button [onClick Add] [text "add"],
div [] [text model.answer]
]
main =
Browser.sandbox
{ init = init,
update = update,
view = view
}

You define the Msg type as
type Msg =
Add String| Minus String
with Add taking a String argument, but when you use it here:
button [onClick Add] [text "add"],
you're not giving it any argument at all.
The underlying issue seems to be that your mental model of the Elm Architecture is wrong. You seem to consider messages as "operations" or function calls rather than events, where Add is a function that takes an argument to apply to the model.
You should instead consider a message as a description of what triggered it. Instead of Add String, you might call it AddButtonClicked, with no arguments (in this case). Then have the update function do what it should based on what's in the model alone, which I'm guessing is an arithmetic operation on firstNum and secondNum.
But you're also not populating those fields. To do so you need to use the onInput event, which does ask for a message that takes a String. You might add a new message FirstNumChanged String for example, then use it with input like this:
input [ placeholder "Text to reverse", onInput FirstNumChanged, value model.firstNum] [],
I'll leave it to you to figure out how to handle it in update.

Related

Update record field from Collection

I am playing a little bit with Elm these days, but I stuck with a simple case where I want to update a record field. My code is like this:
-- MODEL
initialModel : Model
initialModel =
{ selectedLanguage = "german"
, allCards = Card.cards
}
type alias Msg =
{ description : String
, data : String
, id : String
}
The update function
update : Msg -> Model -> Model
update msg model =
case List.head (model.allCards) of
Just card ->
{ card | fliped = True }
Nothing -> model
but I see this:
Something is off with the 1st branch of this `case` expression:
50| { card | fliped = True }
^^^^^^^^^^^^^^^^^^^^^^^^
The 1st branch is a record of type:
{ back : String, fliped : Bool, front : String, id : String }
But the type annotation on `update` says it should be:
Model
Hint: Seems like a record field typo. Maybe back should be allCards?
Hint: Can more type annotations be added? Type annotations always help me give
more specific messages, and I think they could help a lot in this case!
Detected errors in 1 module.
I think I should always return a model from update function like my type says, but cannot figure out how. Any advice here?
You'll have update the allCards field of model too. You can nest the card update inside the model update if the former returns a list instead of just a single card:
update : Msg -> Model -> Model
update msg model =
{ model
| allCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
}
Or you can bind the new allCards to a name if you prefer:
update : Msg -> Model -> Model
update msg model =
let
newAllCards =
case model.allCards of
card :: rest ->
{ card | fliped = True } :: rest
[] ->
[]
in
{ model | allCards = newAllCards }
I pattern match directly on the list here instead of using List.head, as that also gives me the remainder of the list and I don't have to deal with an intermediary Maybe value (or two actually, since List.tail returns a Maybe as well). The card::rest branch hits if allCards contains at least one card, so the only remaining case is therefore [], which is easy enough to handle.
Also, flipped is spelled with two ps ;)

Msg's with extra variables in elm inputs

I am trying to partition my Msg values in Elm (0.18) into different types. I'd like it to conform to this kind of typing:
type MsgSession
= LogIn
| LogOut
type MsgSettings
= SetUsername String
= SetPassword String
type Msg
= SessionMsg MsgSession
| SettingsMsg MsgSettings
...
My update function looks like this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SessionMsg sessionMsg ->
sessionUpdate sessionMsg model
SettingsMsg settingsMsg ->
settingsUpdate settingsMsg model
...
...which means that I have to define the child updates as well...
sessionUpdate : MsgSession -> Model -> ( Model, Cmd Msg )
sessionUpdate msg model =
case msg of
LogIn ->
-- do stuff
LogOut ->
-- do stuff
My LogOut event looks like this, and works fine:
button [onClick (SessionMsg LogOut)] [text "Log Out"]
But once there is a variable involved with the event, it doesn't work. I have a similar setup for settingsUpdate:
settingsUpdate : MsgSettings -> Model -> ( Model, Cmd Msg )
settingsUpdate msg model =
case msg of
SetUsername string ->
...
SetPassword string ->
...
But I can't get onInput to send as a variable. For example, this code
input [onInput SettingsMsg SetUsername] []
Yields this error:
Function `onInput` is expecting 1 argument, but was given 2.
483| onInput SettingsMsg SetUsername
Parentheses also don't work.
input [onInput (SettingsMsg SetUsername)] []
yields
The argument to function `SettingsMsg` is causing a mismatch.
483| SettingsMsg SetUsername)
^^^^^^^^^^^
Function `SettingsMsg` is expecting the argument to be:
MsgSettings
But it is:
String -> MsgSettings
What do I have to pass into onInput to make this work?
You should be able to use composition:
input [onInput (SettingsMsg << SetUsername)] []
Which, if you're more comfortable with explicit lambdas, looks like this:
input [onInput (\name -> SettingsMsg (SetUsername name))] []

onInput argument failure (by Elm n00b)

I am experimenting with Elm based on their tutorial, and is encountering a problem with function argument declaration.
Basically I just extracted a function within the tutorial code. It works fine without function declaration, but fails when I include it.
The essence of the code is:
type Msg
= Name String
| Password String
view : Model -> Html Msg
view model =
div []
[ myInput "text" "Name" Name
]
myInput : String -> String -> Msg -> Html Msg
myInput type__ label handle =
input [ type_ type__, placeholder label, onInput Name ] []
And the error message is:
The 3rd argument to function myInput is causing a mismatch.
47| myInput "text" "Name" Name
Function myInput is expecting the 3rd argument to be:
Msg
But it is:
String -> Msg
Hint: It looks like a function needs 1 more argument.
Ideally I would also like the argument to onInput be the argument called "handle", declared in myInput.
The type signature for myInput is incorrect. The constructor Name has a single argument, which means when used as a function its signature is (String -> Msg). That is what you should use for the annotation of the handle parameter.
myInput : String -> String -> (String -> Msg) -> Html Msg
myInput type__ label handle =
input [ type_ type__, placeholder label, onInput handle ] []

How to migrate from working input range to elm-mdl Slider?

I currently have a function that I use for a range, and number input type for each piece of data (trait). Updates to the model are done via EditTrait Mhid Relevance message.
valueRange : String -> TraitWithRelevance -> Html Msg
valueRange typ trait =
let
( name, mhid, relevance ) =
trait
in
input [ Attr.type_ typ, Attr.min "0", Attr.max "100", Attr.value relevance, Attr.step "1", onInput <| EditTrait mhid ] []
In an attempt to bring in Google's material design through elm-mdl, I want to replace the valueRange "range" call with the valueSlider function which utilizes the Slider component from elm-mdl.
The code below compiles, but obviously doesn't work, because the important onChange handler is missing. However, it renders correctly and the slider changes, when I update a trait's relevance value through the input number element.
valueSlider trait =
let
( name, mhid, relevance ) =
trait
relAsFloat =
String.toFloat relevance |> Result.toMaybe |> Maybe.withDefault 0
in
Slider.view
[ Slider.value relAsFloat
, Slider.min 0
, Slider.max 100
, Slider.step 1
]
When I throw in Slider.onChange (EditTrait mhid), which works on the regular input, the compiler gives me this error.
The argument to function onChange is causing a mismatch.
438| Slider.onChange (EditTrait mhid)
^^^^^^^^^^^^^^ Function onChange is expecting the argument to be:
Float -> m
But it is:
Relevance -> Msg
Detected errors in 1 module.
As onInput type is (String -> msg) -> Html.Attribute msg I suppose Relevance is a String, and EditTrait is mhid -> String -> Msg.
In this case, Slider.onChange (EditTrait mhid) doesn't work because Slider.onChange expects a Float -> Msg not Relevance -> Msg (as the compiler message reads.)
To solve this issue, you should change EditTrait to receive a Float instead of String. Changing Relevance type to be a Float and updating the code accordingly should do the trick.

Elm: Use `Address String Action`

The canonical example for getting the value from an input is:
view : Address String -> String -> Html
view address string =
div []
[ input
[ placeholder "Text to reverse"
, value string
, on "input" targetValue (Signal.message address)
, myStyle
]
[]
, div [ myStyle ] [ text (String.reverse string) ]
]
I get this. But I want my address to be of type Address String Action (where Action is some other type I define). To my understanding, this would mean the address expects a String followed by a Action type as it's "arguments" (I think of Address as a function, but that might not be correct).
Is it possible to use an address type of Address String Action, and then use it with an input in a similar way? Or am I allowed to do Address String Action in the first place?
The example you link to is probably a bit too simplistic in that both the Action and Model are a string. You will seldom run into that.
I've tweaked the example with something that is more canonical to elm in its current form:
main =
StartApp.start { model = { text = "" }, view = view, update = update }
type Action
= SetText String
type alias Model =
{ text : String }
update : Action -> Model -> Model
update action model =
case action of
SetText text ->
{ model | text = text }
view : Address Action -> Model -> Html
view address model =
div []
[ input
[ placeholder "Text to reverse"
, value model.text
, on "input" targetValue (Signal.message address << SetText)
, myStyle
]
[]
, div [ myStyle ] [ text (String.reverse model.text) ]
]
Notice how the Action type is a union type listing all the different ways you can interact with the page. In this example, the only thing you can do is to set the text.
The signature of view is now more explicit. The first argument is the address of a mailbox that deals in type Action, and the second argument contains the current state of the model.
view : Address Action -> Model -> Html
There is no need to go down a path of trying something like Address String Action since now Action encapsulates the setting of the text.