How do I use a Maybe (List a) in a map? - elm

I have a weird situation that I just can't figure out.
type alias Schedule =
{ name : String
, display : String
, interval : Maybe Int
, events : Maybe (List Event)
}
type alias Event =
{ schedule : Maybe String
, interval : Maybe Int
, hook : String
, args : List ( String, String )
, timestamp : Int
, seconds_due : Int
}
setScheduledEventDueNow : Event -> Schedule -> Schedule
setScheduledEventDueNow event schedule =
case schedule.events of
Just events ->
{ schedule | events = List.map (setMatchedEventDueNow event) events }
Nothing ->
schedule
There may or may not be a List of Events in the Schedule, so it's set as events : Maybe (List Event).
In response to an action on a known Event I want to run through the events on a schedule if it has any, and potentially return an updated list of events for the schedule.
However, I'm getting the following error:
-- TYPE MISMATCH ----------------------------------------- src/elm/CronPixie.elm
The 1st and 2nd branches of this `case` produce different types of values.
373| case schedule.events of
374| Just events ->
375| { schedule | events = List.map (setMatchedEventDueNow event) events }
376|
377| Nothing ->
378|> schedule
The 1st branch has this type:
{ a | events : List Event }
But the 2nd is:
{ a | events : Maybe (List Event) }
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.
My beginner brain thought because I had verified that there really was a list of events with the case statement, that all should be good to map it.
But the compiler is recognising that it's a simple List.map with no chance of a Maybe, so naturally complains about the difference between what is returned by the two branches of the case.
Any idea how I get around this?

You need to wrap the list using Just
Just events ->
{ schedule | events = Just <| List.map (setMatchedEventDueNow event) events }
You could write the whole function more succinctly by using Maybe.map, which returns Nothing if the list of events is already Nothing, and maps to the underlying list using the provided function otherwise:
setScheduledEventDueNow : Event -> Schedule -> Schedule
setScheduledEventDueNow event schedule =
{ schedule | events = Maybe.map (List.map <| setMatchedEventDueNow event) schedule.events }

Related

Dynamic form with composable-form

I'm trying to implement a dynamic form in Elm 0.19 using hecrj/composable-form.
I receive a json with the fields, their descriptions, etc, so I don't know beforehand how many fields it will have.
So the traditional way of defining a form:
Form.succeed OutputValues
|> Form.append field1
|> Form.append field2
doesn't work because I don't know the OutputValues structure beforehand.
I've seen there is a function Form.list which looks like a promising path, though it seems to expect all fields equal, which is not my case, I may have a text field and a select field for example.
Is there any straight forward way of doing this with this library?
Thank you.
The form library doesn't explicitly support what you're trying to do, but we can make it work!
tldr;
Here's my example of how you can take JSON and create a form: https://ellie-app.com/bJqNh29qnsva1
How to get there
Form.list is definitely the promising path. You're also exactly right that Form.list requires all of the fields to be of the same type. So let's start there! We can make one data structure that can hold them by making a custom type. In my example, I called it DynamicFormFieldValue. We'll make a variant for each kind of field. I created ones for text, integer, and select list. Each one will need to hold the value of the field and all of the extras (like title and default value) to make it show up nicely. This will be what we decode the JSON into, what the form value is, and what the form output will be. The resulting types looks like this:
type alias TextFieldRequirements =
{ name : String
, default : Maybe String
}
type alias IntFieldRequirements =
{ name : String
, default : Maybe Int
}
type alias SelectFieldRequirements =
{ name : String
, default : Maybe String
, options : List ( String, String )
}
type DynamicFormFieldValue
= TextField String TextFieldRequirements
| IntField Int IntFieldRequirements
| SelectField String SelectFieldRequirements
To display the form, you just need a function that can take the form value and display the appropriate form widget. The form library provides Form.meta to change the form based on the value. So, we will pattern match on the custom type and return Form.textField, Form.numberField, or Form.selectField. Something like this:
dynamicFormField : Int -> Form DynamicFormFieldValue DynamicFormFieldValue
dynamicFormField fieldPosition =
Form.meta
(\field ->
case field of
TextField textValue ({ name } as requirements) ->
Form.textField
{ parser = \_ -> Ok field
, value = \_ -> textValue
, update = \value oldValue -> TextField value requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
}
}
IntField intValue ({ name } as requirements) ->
Form.numberField
{ parser = \_ -> Ok field
, value = \_ -> String.fromInt intValue
, update = \value oldValue -> IntField (Maybe.withDefault intValue (String.toInt value)) requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
, step = Nothing
, min = Nothing
, max = Nothing
}
}
SelectField selectValue ({ name, options } as requirements) ->
Form.selectField
{ parser = \_ -> Ok field
, value = \_ -> selectValue
, update = \value oldValue -> SelectField value requirements
, error = always Nothing
, attributes =
{ label = name
, placeholder = ""
, options = options
}
}
)
Hooking this display function up is a bit awkward with the library. Form.list wasn't designed with use-case in mind. We want the list to stay the same length and just be iterated over. To achieve this, we will remove the "add" and "delete" buttons and be forced to provide a dummy default value (which will never get used).
dynamicForm : Form (List DynamicFormFieldValue) (List DynamicFormFieldValue)
dynamicForm =
Form.list
{ default =
-- This will never get used
TextField "" { name = "", default = Nothing }
, value = \value -> value
, update = \value oldValue -> value
, attributes =
{ label = "Dynamic Field Example"
, add = Nothing
, delete = Nothing
}
}
dynamicFormField
Hopefully the ellie example demonstrates the rest and you can adapt it to your needs!

Function with extensible record parameter passed as a parameter cannot be used from within a case statement

I'm trying to extract some information from some records using extensible records. If I create a function taking the extensible record type and returning a string and use that within a case statement then there are no issues (namedToString, in the below example). However, if I attempt to use a function passed as a parameter (stringFromNamed) I will get an error complaining:
This business value is a:
Business
But stringFromNamed needs the 1st argument to be:
Named a -> String (edited)
from the example code below:
type alias Named a =
{ a | name : String }
type alias Person =
Named { address : String }
type alias Business =
Named { employeeCount : Int }
type Change
= PersonUpdate Person
| BusinessUpdate Business
namedToString : Named a -> String
namedToString changeFields =
changeFields.name
changeToString : (Named a -> String) -> Change -> String
changeToString stringFromNamed change =
case change of
PersonUpdate person ->
-- This works
namedToString person
BusinessUpdate business ->
-- This will cause the error
stringFromNamed business
This example includes just the required code but a more complete example can be found at https://ellie-app.com/77nCPLh55j3a1
What is causing the issue and how can I achieve my goal of passing in a function which will extract some information from an extensible record?
I discovered this issue in the Elm compiler repo: https://github.com/elm/compiler/issues/1959
This appears to be a bug in the compiler which can be worked around if you just want to pass an external record of one type to a function parameter. You can do this by removing the type signature of the higher order function, changeToString in the example above.
Unfortunately, if we wanted to perform the same action from the function parameter, e.g. stringFromNamed, on every case the error will return so another workaround must be found.
The workaround I'm using is to create a new record type which includes exactly the fields from the extensible record and then creating an instance of this from the fields of the other records adhering to the extensible record type. Not a big problem with only a couple of cases and an extensible record with only one field but this doesn't scale especially well. Example below:
type alias Named a =
{ a | name : String }
type alias OnlyNamed =
Named {}
type alias Person =
Named { address : String }
type alias Business =
Named { employeeCount : Int }
type Change
= PersonUpdate Person
| BusinessUpdate Business
type Msg
= UpdateCurrentChange Change
namedToString : Named a -> String
namedToString changeFields =
changeFields.name
changeToString : (OnlyNamed -> String) -> Change -> String
changeToString stringFromNamed change =
case change of
PersonUpdate { name } ->
stringFromNamed { name = name }
BusinessUpdate { name } ->
stringFromNamed { name = name }
I think the issue is that the function you're passing in takes Named a, and Elm doesn't know what the a refers to, so it ends up deciding it's a type mismatch. If you change the type annotation to (Named Business -> String) -> Change -> String it will compile and work.

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 ;)

TYPE MISMATCH - This function cannot handle the argument sent through the (|>) pipe:

I am a super elm begginer and trying to make app.
Currently I am struggling to make landing page and http request to a server.
But, I am stuck here...
I have init function something like this below.
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
Model key TopPage
|> goTo (Route.parse url)
The definition of my Model is below.
-- MODEL
type alias Model =
{ key : Nav.Key
, page : Page
, name : String
, tags : List Tag
, jwt : String }
and, goTo function is below.
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Nothing ->
( { model | page = NotFound }, Cmd.none )
Just Route.Top ->
( { model | page = TopPage }, Cmd.none )
...
type Route is below.
type Route
= Top
| User String
| Repo String String
parse : Url -> Maybe Route
parse url =
Url.Parser.parse parser url
parser : Parser (Route -> a) a
parser =
oneOf
[ map Top top
, map User string
, map Repo (string </> string)
]
but following error has occured.
-- TYPE MISMATCH -------------------------------------------------- src/Main.elm
This function cannot handle the argument sent through the (|>) pipe:
54| Model key TopPage
55| |> goTo (Route.parse url)
^^^^^^^^^^^^^^^^^^^^^
The argument is:
String -> List Tag -> String -> Model
But (|>) is piping it a function that expects:
Model
What did I make mistake here?....
Your Model type has five fields, but in the line
Model key TopPage
you are only providing values for the first two of the five. You are missing values for the name, tags and jwt fields. Provide values for these and the problem should go away.
When you declare a type alias such as Model, Elm creates a constructor function also named Model. Elm functions support partial application, in that if you pass in values for some but not all of the arguments, you end up with a function that takes in the rest of the values. You provided two arguments, so you end up with a function that takes three arguments and returns a Model.
There are two ways of building a value of a type. Given a simple example of a Person type alias:
type alias Person = { name : String, age : Int }
You can construct a value by specifying all fields (note that you don't have to specify Person in the constructor; Elm's compiler is smart enough to know it by its shape):
jane : Person
jane = { name = "Jane", age = 35 }
Or you can build a value by using the type name and specify each field's values in the order in which they were defined. In this style, you can think of Person acting like a function with two parameters that returns a Person value.
jane : Person
jane = Person "Jane" 35
In each case, you have to specify all fields of the type when you construct it in order to obtain a complete Person value. However, that is not the complete story. It is possible to leave off the age parameter when constructing a Person, but the result isn't a Person, it's a function that takes an age and returns a Person. In other words,
janeAged : Int -> Person
janeAged = Person "Jane"
You can strip off as many parameters from the end as you'd like to make more variations on that constructor, even stripping out all parameters:
somebody : String -> Int -> Person
somebody = Person
Back to your example. You are constructing a Model value by only specifying two parameters (Model key TopPage). The value of that expression does not result in a Model, but in a function that takes three more parameters to create a Model. And that's why the error message indicated you need three parameters to construct a model.
You need to specify all values of Model when creating it.

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