I am just starting out learning Elm and I'm having some trouble understanding why I'm getting a type mismatch when passing a custom type into a method that expects... well, what I'm calling a partial type annotation.
Here's the code I'm using:
import Graphics.Element exposing (show)
import Debug
type User =
User { username : String, followers : List User }
type Action = Follow
fromJust : Maybe a -> a
fromJust x = case x of
Just y -> y
Nothing -> Debug.crash "error: fromJust Nothing"
update : User
-> Action
-> { user | followers : List User }
-> { user | followers : List User }
update actor action user =
case action of
Follow -> { user | followers = user.followers ++ [actor] }
getUsers : List User
getUsers =
[
User { username = "UserA", followers = [] },
User { username = "UserB", followers = [] }
]
main =
let
users = getUsers
first = fromJust (List.head users)
last = fromJust (List.head (List.reverse users))
in
show (update first Follow last)
And the error output from elm-lang.org/try:
Type Mismatch
The 3rd argument to function update is causing a mismatch.
43| show (update first Follow last)
Function update is expecting the 3rd argument to be:
{ a | followers : List User }
But it is:
User
Hint: I always figure out the type of arguments from left to right. If an
argument is acceptable when I check it, I assume it is "correct" in subsequent
checks. So the problem may actually be in how previous arguments interact with
the 3rd.
If I change the type annotation for update to expect a User instead, I get a different Type Mismatch, saying I should change the types back. :confused:
It's because of the recursive type. Elm doesn't seem to have particularly intuitive (to a Haskeller anyway) handling of these, and you have to put some explicit unwraps and wraps in there to make it all work. I assume the compiler guided you to the definition of the User type that you have, as it did me when I started playing around with it.
The result of it all is though, values of type User in your example aren't records, they're data constructors which take records as a parameter. You have to pattern match against those values in order to get the record out, and remember to include the constructor whenever you return such a value. Fortunately Elm has good pattern matching support or this would be horrific.
So if you change your update function to
update : User
-> Action
-> User
-> User
update actor action (User user) =
case action of
Follow -> User { user | followers = user.followers ++ [actor] }
it all works. Note that the parameters are now all User types, and the user parameter is being deconstructed so we can get at the record inside. Finally, the result is reconstructed with the User data constructor.
Related
I have an Elm file with some HTML content in a function called view.
Part of the HTML content is dynamically generated via a map function that extracts data from a record.
Below is the code from that file.
module PhotoGroove exposing (main)
import Html exposing (div,h1,img,text)
import Html.Attributes exposing (..)
--model
initialModel : { photos : List { url : String }, selectedPhoto : String }
initialModel =
{ photos =
[ { url = "1.jpeg" }
, { url = "2.jpeg" }
, { url = "3.jpeg" }
]
, selectedPhoto = "1.jpeg"
}
--view
urlPrefix : String
urlPrefix = "http://elm-in-action.com/"
viewThumbnail : String -> { a | url : String } -> Html.Html msg
viewThumbnail selectedPhoto thumb =
if selectedPhoto == thumb.url
then img [src (urlPrefix ++ thumb.url), class "selected"] []
else img [src (urlPrefix ++ thumb.url)] []
view : List { a | url : String } -> Html.Html msg
view model =
div [class "context"]
[h1 [] [text "Photo Groove"]
,div [id "tumbnail"] (List.map viewThumbnail model) -- error message on "model"
]
--main
main = view initialModel -- error message on "initialModel"
However, I got error messages on the words "model" and "initialModel".
Below is the error message for "model":
TYPE MISMATCH - The 2nd argument to `div` is not what I expect:
29| ,div [id "tumbnail"] (List.map viewThumbnail model)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
This `map` call produces:
List #({ a | url : String } -> Html.Html msg)#
But `div` needs the 2nd argument to be:
List #(Html.Html msg)#
#Hint#: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
And for initialModel:
TYPE MISMATCH - The 1st argument to `view` is not what I expect:
35| main = view initialModel
#^^^^^^^^^^^^#
This `initialModel` value is a:
#{ photos : List { url : String }, selectedPhoto : String }#
But `view` needs the 1st argument to be:
#List { a | url : String }#
I got that I need to somehow convert the objects directly into HTML msg types, but I don't know how.
I even tried to change the type for view, but that did not work either.
How do I fix this problem?
This is a great example of Elm's type system (or a good static type system in general) helping you figure out logical errors in your code.
The problems here are all due to your view function, and in particular with faulty assumptions it makes about what is in your model.
You are trying to map a function over the model argument, which assumes model is a list - but it isn't, it's a record containing both a list of all photos and a string identifying the selected photo. That makes logical sense as a model to use in this situation, so rather than changing the model type (which would otherwise be a sensible option) I would suggest changing view so that it works with your actual model type.
Another problem, which is highlighted in the first of the 2 error messages you quote, is that viewThumbnail can't be mapped over a list of photos anyway because it actually takes 2 arguments, the first of which isn't a list. You do want to use this function to produce the list of images in view's output - but you also need to provide it with the selectedPhoto, which of course comes from the other field in your model that you've ignored so far.
So the only changes needed to your original code to get this to compile are:
change the type signature of view to actually match your model's type. (This is what the second of the two error messages is about). It should be view : { photos : List { url : String }, selectedPhoto : String } -> Html.Html msg (You probably want to make a type alias for this model type, at least if you expand this app to any much greater size.)
inside the function body,
change this
List.map viewThumbnail model
to
List.map (\photo -> viewThumbnail model.selectedPhoto photo) model.photos
Hopefully you can see what the above is doing - the \ is for an anonymous function, that takes one of the photos and calls viewThumbnail with that as second argument and model.selectedPhoto always as the first argument.
And note finally that you can simplify the above by taking advantage of Elm's built-in function currying, to just
List.map (viewThumbnail model.selectedPhoto) model.photos
which I and probably most others familiar with functional programming find much cleaner and easy to read.
I'm teaching myself elm and see (of course) many references to Html msg--
I understand that this is a 'parameterised type', that is, that (as I understand it), the constructor of the type Html takes a parameter -- much as List Char does.
OK. But then in following along in some tutorials, I see they quickly change the msg to be a custom type, often something like this (I'm doing this from memory, so please forgive me):
Html Msg
where Msg might be defined as
type Msg =
Something | SomethingElse
I also see that here -- https://discourse.elm-lang.org/t/html-msg-vs-html-msg/2758 -- they say
The lower case msg is called a type variable. The upper case Msg is called a concrete type - its something your application has defined.
That answers my question part way, but could someone elaborate on what this means exactly? Because when I see something like List String, I understand what String is in this context, but I don't understand what msg means in Html msg.
Also, are we not changing the type of the return value of the function? That is, if the Elm runtime is expecting view to return a certain type, and that type is Html msg, if we change the return type to Html Whatever, why does that work? (For instance, we can't change a function's return value from List String to List Number arbitrarily, right?)
Coming from a background in OOP and typed languages like C, TypeScript, etc, I would think that any Msg would need to somehow be related to msg, that is 'extend' it in some way to allow polymorphism. Obviously I'm looking at this the wrong way, and any explanation would be appreciated!
tl;dr: as long as every function agrees on the type of msg, and model, they can be anything you want.
Just like the type parameter of List, msg can be anything. There is no restriction. To demonstrate, here's the classic increment/decrement example with the msg being just an Int:
-- These type aliases aren't needed, but makes it easier to distinguish
-- their roles in later type signatures
type alias Model = Int
type alias Msg = Int
update : Msg -> Model -> Model
update msg model =
model + msg
view : Model -> Html Msg
view model =
div []
[ button [ onClick 1 ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick (-1) ] [ text "-1" ]
]
main : Program () Model Msg
main =
Browser.sandbox { init = 0, view = view, update = update }
So how does that work? The key is in the Browser.sandbox function, as that's what connects everything together. Its type signature is:
sandbox :
{ init : model
, view : model -> Html msg
, update : msg -> model -> model
}
-> Program () model msg
where model and msg are type variables. Both of these can be replaced with any concrete type, as long as it matches up with the constraints given in this signature, which is that they must be the same across the init, view and update functions. Otherwise we'll get a type error here. If, for example, update requires a String instead of an Int, we'll get the error:
This argument is a record of type:
{ init : Model, update : String -> Model -> Model, view : Model -> Html Msg
}
But `sandbox` needs the 1st argument to be:
{ init : Model
, update : String -> Model -> Model
, view : Model -> Html String
}
It doesn't really know which is right or wrong, of course, just that there's a mismatch. But in an effort to be helpful it assumes String is right and expects view to return Html String instead.
I hope that sufficiently answers your question. If not, comment and subscribe!
Short and Simple Explanation:
I have an xmas function like this:
wrapPresents : presents -> String
But what is the type, presents? It can be anything that you want! You can substitute it with your own 'concrete type'. presents is just a place holder!
wrapPresents : Boxes -> String
Now we have the "type" - we know that the first parameter must be a Box type.
wrapPresents boxes = "All the boxes are now wrapped!"
Summary: msg (lowercase) is simply a placeholder
I'm building an application in Elm where most API calls are protected; i.e. the user needs to be logged in for the API call to work. If the user is not logged in, they will receive a 401 Unauthorized response. I want the application to redirect to the login page if any response is a 401.
Currently, I only have this redirect set up for a single API call. Here is a stripped-down version of the code to give an idea of how it's set up:
-- Util/Api.elm
type alias Data data =
{ data : data
}
-- Resources/Expense.elm
getExpenses : (Progress (Api.Data (List Expense)) -> msg) -> Sub msg
getExpenses msg =
(dataDecoder expenseListDecoder)
|> Http.get expensesEndpoint
|> Progress.track expensesEndpoint msg
-- Main/Msg.elm
type Msg
= ExpenseListMsg ExpenseListMsg
| RedirectToLogin
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ExpenseListMsg msg ->
ExpenseList.Update.update msg model
GoTo path ->
model ! [ Navigation.newUrl path ]
RedirectToLogin ->
model ! [ Navigation.load "path/to/login" ]
-- ExpenseList/Msg.elm
type ExpenseListMsg
= GetExpensesProgress (Progress (Api.Data (List Expense)))
| SetLoading
-- ExpenseList/Update.elm
update : ExpenseListMsg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
SetLoading ->
{ model | expenses = setExpensesLoading model.expenses } ! []
GetExpensesProgress (Done { data }) ->
{ model | expenses = addExpenses model.expenses data } ! []
GetExpensesProgress (Fail (BadStatus { status })) ->
case status.code of
401 ->
model ! [ msgToCmd RedirectToLogin ]
_ ->
model ! []
GetExpensesProgress (Fail error) ->
model ! []
GetExpensesProgress progress ->
{ model | expenses = setExpensesLoading model.expenses } ! []
Essentially, I want to move the logic around 401 responses from ExpenseList/Update.elm up to Main/Update.elm so that I can use it for any request I want.
I attempted a number of things, but nothing quite worked with Elm's type system. For example, one thing I wanted to do was try to do a nested pattern match with missing specificity in the middle, e.g.:
-- Main/Update.elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ApiCall (messageType (msg (Fail (BadStatus { status })))) ->
case status of ->
. . .
. . .
I was hoping something like this would work and would match a message that looked like: ApiCall (ExpenseListMsg (GetExpensesProgress (Fail (BadStatus)))). Unfortunately, it's not proper Elm syntax, so my code didn't compile.
How can I write something that will allow me to mark an API call as protected and catch 401 errors at the top level in Main.Update.update?
Currently, the API call is encapsulated by the ExpenseList/Update module. This encapsulation is what makes the API call results unavailable to the Main module. The interaction works like this: Main -> FeatureModule -> API
Because the API is what provides the information needed to determine whether the app should redirect to the login page, and you want the Main module to perform the redirect, the Main module needs access to the API. Hence, the encapsulation needs to go. Instead, you can:
Have an API module which provides the low-level API functionality by producing Tasks. Unlike producing Cmds, this allows the caller, such as the Main module to decide how to handle the result of the Task, which can be acquired by converting the Task to a Cmd and giving it to the Elm runtime for execution.
Have the ExpenseList.Update module use the API module to create Tasks.
With this arrangement:
The Main module sends high-level commands to a feature module, which then uses the API module to produce the low-level instructions, which are then provided to the Main module.
The Main module doesn't need to care what those low-level instructions are, it simply converts the Task to a Cmd and waits for the result.
When the result comes back it's in a low-level format (ex. Success/Fail). At this point the Main module can jump in and handle the redirect on a 401 error. Otherwise, it can pass the result to a feature module so it may handle the result.
Let's say I am trying to follow the Elm architecture and split my workflow into Users and Invoices while using StartApp.
Users have invoices, but they must log in to access them.
The model may look like something along these lines:
type Model
= NotLoggedIn Credentials
| LoggedIn RealName (Maybe Invoices)
type alias State =
{ login : Model
, notification : ......
, ......
type alias Invoices = { invoices: List Invoice, ...... }
User module has actions:
type Action
= Login (Result Http.Error String)
| Logout
| Submit
...
and update function:
update : Action -> Model -> (Model, Effects Action, Notification)
update action user =
case (action, user) of
(Login res, _) ->
case res of
Ok name ->
(LoggedIn name Nothing, Effects.none, Info "Welcome!")
...
I skip the details of authentication, it's all good. The interesting part is Login action. The tuple is sent to the step function in main:
step : Action -> State -> (State, Effects Action)
step action state =
case action of
UserAction a ->
let (newstate, ef, n) = User.update a state.login
in ({ state | login = newstate, notification = n }, Effects.map UserAction ef)
InvoiceAction a -> ......
So the user has logged in. Next we want to call some init action in the Invoice module.
But how should this be done properly? How to initiate the action of Invoice to preserve incapsulation? Shall I return sometihng other than Effects.none?
This might be a case that could be solved by different modeling of your app's data.
The way I understand it, you have actions that require as user and actions that do not require an user. The InvoiceAction seams to me that it should belong to the UserAction.
So, you could have
type MainAction = UserAction UAction | NonUserAction NonUAction
type UAction = AuthAction Credentials | InvoiceAction Invoice.Action
The user model would encapsulate both login details and invoice details. And then, after a successful log in you could redirect to an InvoiceAction.
update action model =
case action of
AuthAction credentials ->
let
(isLoggedIn, notifications) = Authentication.check credentials
model' = { model | credentials = credentials, notifications = notifications}
in
if isLoggedIn
then update (Invoice.initialize model'.credentials) model'
else (model', Effects.none)
InvoiceAction act ->
let
(invoices, fx) = Invoice.update model.credentials act model.invoices
in
({model | invoices = invoices}, Effects.map InvoiceAction fx)
The actual action is provided by the Invoice module via a function initialize with a signature like initialize: Credentials -> Action. This is done to maintain encapsulation. The User module need not know about particular Invoice actions, only that there is one related to initialization and it can get it via that function.
Also, please note that I've simplified the update signature and moved the notifications inside the model. This is a personal preference as I see notifications as nothing special. They are like any other piece of data in the model. Of course, if the notifications are tasks that get routed via some custom StartApp into a port and get displayed by some JS mechanism, it might make sense to keep them in the return.
One approach will be:
Create a mailbox in the parent component
Pass the address of that mailbox to User update
User update returns an effect that sends a message to this address
Upon receiving a message this mailbox triggers an action that flows to Invoice
This chapter in elm-tutorial shows this pattern.
In our ADFS 3.0 we have a custom attribute-store that communicates with a rest-service to retrieve specific attributes based on the userid. These attributes comes with an urn and a value. My goal was to have the urn retrieved from the rest-service set to the claim-type but this type seems to be set in the so called template you create from the gui of ADFS. See below code:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
=> issue(store = "Custom_AttributeStore", types = ("my:custom:urn"), query = "dummyString", param = c.Value);
Is it possible with adfs claim rule language to set the claim types programmatically in my custom attributestore?
There is a way to dynamically emit any pair of claim type and value from a custom attribute store. This article helped me to figure it out.
Your custom attribute store needs to return a single column of string values, where each value represents a single claim type and its corresponding value, separated by a special character (a semicolon in this example):
public string[][] EndExecuteQuery(IAsyncResult result)
{
...
return new[]
{
new[] { "http://example/custom-type-1;Value 1" },
new[] { "http://example/custom-type-2;Value 2" },
...
};
}
Next, use a custom claim rule to retrieve values from your custom attribute store and create input claims of an arbitrary type from them. Use the add command (which creates input claims) instead of the issue command (which creates output claims).
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"] => add(store = "YourCustomAttributeStore", types = ("custom-type-value-pair"), query = "YourQuery", param = c.Value);
Finally, use another custom claim rule to pick up each of the input claims created in the previous step and use their values to issue the proper output claims. The RegexReplace function can be used to separate type and value.
c:[Type == "custom-type-value-pair"] => issue(Type = RegexReplace(c.Value, ";.*$", ""), Value = RegexReplace(c.Value, "^.*?;", ""));
It may be a little convoluted, but it works.