Elm Multiple Counter code using List - elm

Im new to Elm , previously I was doing React, Redux , ES6
Need help optimizing the below counter example using Array rather than List.
Getting error Type mismatch When I tried to use Array Set/Get methods.
import Html exposing (beginnerProgram, div, button, text, p, Html, span)
import Html.Events exposing (onClick)
import Array
main =
beginnerProgram { model = [0,7], view = view, update = update }
view model =
div []
[ div [] [ button [ onClick AddCounter ] [ text "Add Counter" ] ]
, div [] (List.indexedMap (\index data -> (viewCounter index data)) model)
]
viewCounter: Int -> Int -> Html Msg
viewCounter index data =
div [] [
p [] [text ("Counter " ++ toString index )]
, button [ onClick (Decrement index) ] [ text "-" ]
, span [] [ text (toString data) ]
, button [ onClick (Increment index) ] [ text "+" ]
]
type Msg =
AddCounter
| Increment Int
| Decrement Int
incrementSelectedIndex index selectedIndex data =
if index == selectedIndex then data + 1
else data
decrementSelectedIndex index selectedIndex data =
if index == selectedIndex then data + 1
else data
update msg model =
case msg of
AddCounter ->
List.append model [0]
Increment selectedIndex ->
(List.indexedMap (\index data ->
(incrementSelectedIndex index selectedIndex data)) model)
Decrement selectedIndex ->
(List.indexedMap (\index data ->
(decrementSelectedIndex index selectedIndex data)) model)

Array.get returns a Maybe a since the requested index may not be found. This may be different than other languages with which you have experience, which tend to throw exceptions when an index is out of bounds.
In Elm, the Maybe type has two possible values: Nothing, and Just a where a is the actual value you care about.
You can use a case statement to differentiate between the two options. Here is an example of what your Increment case can look like using Array's get and set functions:
Increment selectedIndex ->
case Array.get selectedIndex model of
Just val -> Array.set selectedIndex (val + 1) model
Nothing -> model
Notice that if we ask for a nonexistent index, we would get Nothing and just return the original array rather than crashing the program.

Related

How to update specific element in a list in Elm

There are couple apples ( in type of List ) which will expose themselvies in the web view. User can update any size attribute of an Apple. I have a msg type UpdateSize which will be triggered via onInput.
Editing any of the apples will only just trigger the message without knowing which apple to be updated.
Is that possible to pass an id attribute to UpdateSize message?
Thank you for reading this, Elm is great !
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick,onInput)
import String
type alias Apple = {
size: Int}
type alias Model = {
apples: List(Apple)}
initialModel : Model
initialModel =
{ apples = [ Apple 10, Apple 11, Apple 12] }
type Msg
= UpdateSize String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize s -> {model | apples = ??? } -- how to update a single Apple with new size
_ -> model
viewApple : Apple -> Html Msg
viewApple a =
input [ type_ "text" ,placeholder ""
, value (String.fromInt a.size)
, onInput UpdateSize]
[]
view : Model -> Html Msg
view model =
div []
(List.map viewApple model.apples)
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
Code link: https://ellie-app.com/ghd9jrcjKQQa1
With your current implementation it's not possible to know which apple to update since there's no unique attribute about the apples. What if two apples have the same size? If would be better if apples had IDs, or you used a dictionary type to keep track of the apples.
However, for the sake of demonstration, you could say that the list indeces of the apples are unique and you find them accordingly. In real life this will be a fragile solution.
Here's a naive approach using some helper functions from List.Extra.
-- ...
type alias Size =
Int
type Msg
= UpdateSize Int String
update : Msg -> Model -> Model
update msg model =
case msg of
UpdateSize index sizeStr ->
let
maybeSize =
String.toInt sizeStr
in
maybeSize
|> Maybe.withDefault (\size -> { model | apples = updateApple index size model.apples })
|> model
_ ->
model
updateApple : Int -> Size -> List Apple -> List Apple
updateApple index size apples =
let
maybeApple =
List.Extra.getAt index apples
in
maybeApple
|> Maybe.map (\apple -> List.Extra.setAt index { apple | size = size } apples)
|> Maybe.withDefault apples
-- ...
viewApple : Int -> Apple -> Html Msg
viewApple index a =
input
[ type_ "text"
, placeholder ""
, value (String.fromInt a.size)
, onInput (UpdateSize index)
]
[]
view : Model -> Html Msg
view model =
div []
(List.indexedMap viewApple model.apples)

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

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.

Type error in the update function in Elm

I'm new to elm (0.17) and I try to understand how it works. In this case, I try to develop a kind of project estimation.
This is what I did:
import Html exposing (..)
import Html.App as Html
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- model
type alias Host = {
name : String,
cost : Int
}
type alias Model =
{ email : String
, hosting : List Host
, period : List Int
, interventionDays : List Int
, total : Int
}
init : (Model, Cmd Msg)
init =
(Model "init#email.fr" [{name="AAA", cost=15}, {name="BBB", cost=56}, {name="CCC", cost=172}] [1..12] [1..31] 0, Cmd.none)
type Msg = Submit | Reset
calculate : Int
calculate = 42 -- to test
update : Msg -> Model -> (Model, Cmd Msg)
update action model =
case action of
Submit ->
(model, calculate)
Reset ->
(model, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- view
hostOption host =
option [ value (toString host.cost) ] [ text host.name ]
durationOption duration =
option [value (toString duration) ] [ text (toString duration)]
view : Model -> Html Msg
view model =
Html.form []
[ h2 [] [ text "Estimate your project"]
, input [ placeholder model.email ] []
, select []
(List.map hostOption model.hosting)
, select []
(List.map durationOption model.period)
, select []
(List.map durationOption model.interventionDays)
, Html.span [][text (toString model.total)]
, button [onClick Submit] [text "Submit"]
, button [onClick Reset] [text "Reset"]
]
I think I have understood some ideas behind elm but I need help because elm-make command returns:
The 1st and 2nd branches of this `case` produce different types of values.
40| case action of
41| Submit ->
42| (model, calculate)
43| Reset ->
44|> (model, Cmd.none)
The 1st branch has this type:
( a, Int )
But the 2nd is:
( a, Cmd a )
Hint: All branches in a `case` must have the same type. So no matter which one
we take, we always get back the same type of value.
Detected errors in 1 module.
I understand the problem but I do not know how to fix it. Do I have to define my calculate function to work with model data ?
Thanks
I'm going to guess that you want to update the the total field of your model with calculate.
The first tuple item that the update function returns is the updated model. As things stand, both of your actions return the existing model without changing it. So you could try this:
case action of
Submit ->
({ model | total = calculate }, Cmd.none)
Reset ->
init
See here for the syntax for updating records.
Note that I also changed the Reset branch to return init, the initial model and command.
The compiler error is telling you that the update method, in some cases will return a (Model, Cmd) tuple, and in another cases will return a (Model, Int) tuple.
The update function as you have it, should return the modified model and also a Cmd to execute an action, in other words, a (Model, Cmd) tuple.
If you return (model, calculate) it will return a (Model, Int) tuple, since calculate is an Int. That is what is breaking the compiling.
So to fix it, first you need to decide what to do with each of the Msg. I assume by the name of them that the Calculate Msg will update the total and the Reset Msg will set the model to the default state.
For that you could do:
case action of
Submit ->
({ model | total = calculate }, Cmd.none)
Reset ->
init
In this case, both branches will return a tuple of type (Model, Cmd).
Note that the Reset branch will return init, which is already of type (Model, Cmd).
Check the official guide for more examples: http://guide.elm-lang.org/index.html

In Elm what is the correct way to implement my own toString

In Elm what is the correct way to take my Model and implement a toString function?
The type I am looking for would be toString : Model -> String, I am able to make a similar function with the type of toStr : Model -> String but I would think I would want the function to be called toString.
Example program (the Coin Changer kata):
module CoinChanger where
import Html exposing (..)
import StartApp.Simple as StartApp
import Signal exposing (Address)
import Html.Attributes exposing (..)
import Html.Events exposing (on, targetValue)
import String
---- MAIN ----
main =
StartApp.start
{
model = emptyModel
,update = update
,view = view
}
---- Model ----
type alias Model =
{
change : List Int
}
emptyModel : Model
emptyModel =
{
change = []
}
---- VIEW ----
toStr : Model -> String
toStr model =
model.change
|> List.map (\coin -> (toString coin) ++ "¢")
|> String.join ", "
view : Address String -> Model -> Html
view address model =
div []
[
input
[
placeholder "amount to make change for"
, on "input" targetValue (Signal.message address)
, autofocus True
-- style
]
[]
, div []
[
text (toStr model)
]
]
---- UPDATE ----
changeFor : Int -> List Int
changeFor amount =
[ 25, 10, 5, 1 ]
|> List.foldl
(\coin (change, amount)
-> ( change ++ List.repeat (amount // coin) coin
, amount % coin)
)
([], amount)
|> fst
update : String -> Model -> Model
update change model =
{ model | change =
case String.toInt change of
Ok amount
-> changeFor amount
Err msg
-> []
}
I would think the correct way to do this would be to call the function toString, but that gives me the following error from the compiler:
Detected errors in 1 module.
-- TYPE MISMATCH ----------------------------------------------- CoinChanger.elm
The type annotation for toString does not match its definition.
42│ toString : Model -> String
^^^^^^^^^^^^^^^ The type annotation is saying:
{ change : List Int } -> String
But I am inferring that the definition has this type:
{ change : List { change : List Int } } -> String
Renaming the function to toStr (or something not called toString) fixes the issue but seems wrong. What is the correct way to do this?
The problem is that, calling your function toString, you are overriding the toString function of the Basics module, which you are using at line 45.
To avoid this, you'll need to import the Basics module and use Basics.toString instead of simply toString to eliminare the ambiguity
The accepted answer is well out of date for anyone writing Elm 0.19+. The current solution is to write your own toString function for the type you want converted. There is a Debug.toString for use during development but its use in your code will prevent building for production.

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.