Is it possible to use Maybe with Extensible Records? - elm

I am trying to write a function to pull the maybe off a list of extensible records, I am wondering if this is possible. Source code is below, or see Ellie link here
module Temp exposing (..)
import Html exposing (text)
main =
text "Hello"
items : Maybe List { data | id : Int } -> List { data | id : Int }
items maybeList =
case maybeList of
Just t ->
t
Nothing ->
[]

Maybe List { data | id : Int } parses as Maybe (List) ({ data | id : Int }). I'm not sure why the error message is so misleading, but the fix is to wrap List ... in () like this:
items : Maybe (List { data | id : Int }) -> List { data | id : Int }
^ ^
Edit: also, your function can be simplified using Maybe.withDefault:
items = Maybe.withDefault []

Related

How to trigger Cmd from view in Elm

I'm new to Elm and I ran into this problem...
We get translations for our page using something like:
case (translate translation.id) of
Success: -> translation
Failure: -> translation.id
Where translate just finds translation.id in a dictionary and it may or may not be there.
There are no runtime errors because you get a string either way, but we would like to log the missing translation to a rest service logger. But Elm hates side effects in the view that doesn't stem from html events so I'm not sure how to handle this.
Obviously in regular JS you could just crowbar in a fetch inside the failure case block and then return a string afterwards but that doesn't seem to be possible in Elm?
You need to move the effects part of your code into the update function. In this case I suggest doing it when the translation arrives. There you will want something like
OnTranslation translation ->
( { model | translation = translation } -- attach to model
, case translate translation.id of
Ok _ ->
Cmd.none
Err err ->
-- register with the error logger
logMissingTranslation translation.id
)
#Odin Thorsen, use Html.node and Html.Attributes.attribute to reference a custom element in Elm:
-- VIEW
view : Model -> Html.Html ()
view model =
Html.div []
[ translation model "key"
, translation model "junk key"
]
translation : Model -> String -> Html.Html ()
translation { translations } id =
Html.node "translated-text"
[ Html.Attributes.attribute "translation-id" id
, Html.Attributes.attribute "translation-text" <| translate translations id
]
[]
translate : Dict.Dict String String -> String -> String
translate translations id =
case Dict.get id translations of
Just value ->
value
Nothing ->
""
And in Javascript, use customElements.define to create the custom element:
customElements.define("translated-text", class extends HTMLElement {
constructor() { super(); }
connectedCallback() { }
attributeChangedCallback() { this.setTextAndLogFailure(); }
static get observedAttributes() { return ["translation-id", "translation-text"]; }
setTextAndLogFailure() {
var id = this.getAttribute("translation-id");
var text = this.getAttribute("translation-text");
if (text === null) return;
this.textContent = text;
if (!text.length) alert("Unkown translation id: " + id);
}
});
You'd replace alert("Unkown translation id: " + id); with the logging fetch.
Here is an Ellie with a full solution.

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

Elm: accessing common fields in union

I am trying to model a type as a union where each member of that union has properties in common with all other members.
I am currently achieving this like so:
type alias File = {
name : String
}
type CommonFileState extra = CommonFileState {
id : String
, file : File
} extra
type alias ValidFileState = CommonFileState {
validatedAt : Int
}
type alias InvalidFileState = CommonFileState {
reason : String
}
type alias LoadingFileState = CommonFileState {}
type FileState = Valid ValidFileState | Invalid InvalidFileState | Loading LoadingFileState
Now if I want to read one of those common properties on any given FileState, I must match against each member of the union:
getId : FileState -> String
getId fileState = case fileState of
Valid (CommonFileState {id} extra) -> id
Invalid (CommonFileState {id} extra) -> id
Loading (CommonFileState {id} extra) -> id
This feels wrong to me, because I have to duplicate the property access for each member. If I needed to manipulate this property somehow (e.g. concatenating something onto the string), I would also have to duplicate this.
I want to be able to easily access common properties of my union, and operate on those common properties.
When I started searching for other ways to do this, I found one alternative was to nest the union inside a record, which also holds the common properties:
type alias ValidCurrentFileState = {
validatedAt : Int
}
type alias InvalidCurrentFileState = {
reason : String
}
type alias LoadingCurrentFileState = {}
type CurrentFileState = Valid ValidCurrentFileState | Invalid InvalidCurrentFileState| Loading LoadingCurrentFileState
type alias File = {
name : String
}
type alias FileState = {
id : String
, file : File
, currentState : CurrentFileState
}
getId : FileState -> String
getId {id} = id
However this is awkward because I have to name the nested union, which adds a level of unnecessary indirection: "file state" and "current file state" are conceptually the same.
Are there any other ways of doing this which don't have the problems I mentioned?
I think you are thinking about this the wrong way around.
The purpose of modelling (in Elm) is capture the possible states of your data, and to exclude - in your model - 'impossible' states, so that the compiler can statically prevent the code every creating such states.
Once you're happy with your model, you write the helpers you need to make your core logic easy to express and to maintain.
I suspect I would normally go with your second approach, but I don't know all the issues you need to account for.

Elm union subsets

Say I have a union type like this:
type Route
= Home
| License
| UserProfile { username : String }
| Search { query : String }
| SomeOtherPage
In practice I frequently need to work with subsets of this union. For example:
type StaticRoute = Home | License
I would like to be able to define functions which accept subsets like the above, instead of the wider Route.
I don't want to nest StaticRoute inside of Route, like so:
type Route
= Static StaticRoute
| UserProfile { username : String }
| Search { query : String }
| SomeOtherPage
This is because I want to be able to define many different subsets of Route, some of which could overlap:
type StaticRoute = Home | License
type RouteWithServerRendering = Home | Search { query : String }
type LoggedInRoute = SomeOtherPage
-- and so on…
How then can I define subsets of Route without repeating definitions?
Jasper Woudenberg recently posted Conversion functions, five stars
, which advocates for having similar types and using conversion functions to translate between one type to another.
In your case, it might look like this:
module Route exposing (fromStaticRoute, toStaticRoute)
fromStaticRoute : StaticRoute -> Route
fromStaticRoute staticRoute =
case staticRoute of
Static.Home ->
Home
Static.License ->
License
toStaticRoute : Route -> Maybe StaticRoute
toStaticRoute route =
case route of
Home ->
Just Static.Home
License ->
Just Static.License
_ ->
Nothing

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.