How can I refactor two functions into one function that takes a generic argument? - elm

How can I refactor two functions into one function that has a generic parameter?
Example:
getVideo : Video -> Post
getVideo video =
let
(Video post) =
video
in
post
getPodcast : Podcast -> Post
getPodcast podcast =
let
(Podcast post) =
podcast
in
post
I would like to do something like this:
getPodcast : 'a -> Post
getPodcast 'a =
let
('a post) =
'a
in
post
Appendix:
type Video
= Video Post
type Podcast
= Podcast Post

You cannot have such an open-ended generic function in Elm. Here are two options:
Use a container type
You can create a container type that has a constructor for each of its valid types:
type PostContainer
= VideoContainer Video
| PodcastContainer Podcast
Now your getPost function consists of a case statement which returns the appropriate post.
getPost : PostContainer -> Post
getPost container =
case container of
VideoContainer (Video post) ->
post
PodcastContainer (Podcast post) ->
post
Include the post type in the Post value
Let's say your Post object looks like this:
type alias Post =
{ name : String
, body : String
}
You could create an enumeration of post types like this:
type PostType = Video | Podcast
You could redefine Post to include the type:
type alias Post =
{ name : String
, body : String
, postType : PostType
}
Or, if you choose to keep the post body separate from the type, you could do something like this:
type alias PostContents =
{ name : String
, body : String
}
type Post = Post PostType PostContents
and your getPostContents function would simply be
getPostContents : Post -> PostContents
getPostContents _ contents =
contents

Related

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.

Pattern matching tagged union types

I have a tagged union type that contains some record data, like
type Comment = New Content | Edited Content | Flagged Content
type alias Content = {id: Int, text: String}
where the Comment type declares the state.
When using pattern matching for example to filter by id, I have to write
filter Int -> Comment -> Bool
filter id comment =
case comment of
New content -> content.id == id
Edited content -> content.id == id
Flagged content -> content.id = id
This works, but I have to duplicate the same logic for each case, when instead I'd like it simple as
filter id comment =
case comment of
_ content -> content.id == id
With function such as the filtering this is simple one line duplicate, but when rendering the content based on state, the duplication of HTML generation logic is more serious.
I understand that in Elm union types can carry a different "payload" and the compiler does not like the generic version, but in case such as this is there some way to tell the compiler that all these cases are handling the same record type?
Or is this a case of invalid use of union types and the model should be structured differently? Maybe with the state being part of the record type.
Or is this a case of invalid use of union types and the model should be structured differently?
If all the three variants will always contain the same data, then yes.
I'd use a record at the top and create a tagged union for the "Status" of the Comment.
type alias Comment =
{ id : Int
, text : String
, status : Status
}
type Status
= New
| Edited
| Flagged
This will make it easy to access id and text of a comment. You'll still get the benefits exhaustive pattern matching if you do case comment.status of ....
You could factor out the get content part
type Comment = New Content | Edited Content | Flagged Content
type alias Content = {id: Int, text: String}
filter : Int -> Comment -> Bool
filter id comment =
let content = commentContent comment
in content.id == id
commentContent : Comment -> Content
commentContent comment =
case comment of
New content -> content
Edited content -> content
Flagged content -> content
You can abstract even further, for example if you want to add a text filter
filter : Int -> Comment -> Bool
filter id comment =
doFilter (\c -> c.id == id) comment
filterText : String -> Comment -> Bool
filterText text comment =
doFilter (\c -> c.text == text) comment
doFilter : (Content -> Bool) -> Comment -> Bool
doFilter f comment =
let content = commentContent comment
in f content
And finally, add some functional style...
doFilter : (Content -> Bool) -> Comment -> Bool
doFilter f = f << commentContent

Avoid Http Race Condition in Elm

Let's assume we have a text input field and on every change of its content we send an Http request to a search API. Now, we don't have any guarantee that the Http responses get back to elm in the same order that we sent the requests.
What's the easiest way to make sure we react to the response corresponding to the latest request – rather than the latest response, which might correspond to an outdated search string? Is there an easy way to attach the query string to the message returned by Elm's http effect? Or any other way we can link the response to the request by which it was triggered?
I'd like to avoid including the query in the response of the search API if possible. Another remedy would be to debounce the search, but that would just decrease the probability of using the wrong response, whereas we'd like to eliminate it.
Thanks for your help!
Example:
import Html
import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode
main = Html.program
{ init = ( { searchText = "", result = "" }, Cmd.none )
, update = update
, subscriptions = (\model -> Sub.none)
, view = view
}
type alias Model =
{ searchText : String
, result: SearchResult
}
type alias SearchResult = String
type Msg
= NewSearchText String
| ReceivedResponse (Result Http.Error SearchResult)
update msg model =
case msg of
NewSearchText newText ->
( { model | searchText = newText}
, getSearchResult newText
)
ReceivedResponse (Result.Ok response) ->
( { model | result = response }
, Cmd.none
)
ReceivedResponse (Result.Err error) ->
Debug.crash <| (toString error)
getSearchResult : String -> Cmd Msg
getSearchResult query =
let
url = "http://thebackend.com/search?query=" ++ query
request : Http.Request SearchResult
request = Http.get url Decode.string
in
Http.send ReceivedResponse request
view model =
div []
[ Html.input [onInput (\text -> NewSearchText text)] []
, Html.text model.result
]
Yes, it is possible to attach the query string to the response. First, augment your message type to handle the additional data:
type Msg
= NewSearchText String
| ReceivedResponse String (Result Http.Error SearchResult)
Then, change your Http.send call to attach the query text to the ReceivedResponse message:
Http.send (ReceivedResponse query) request
Finally, in your update, grab the query in your pattern match on the resulting Msg:
case msg of
ReceivedResponse query (Ok response) ->
...
ReceivedResponse query (Err err) ->
...
Why does this work?
The Http.send function's first argument can be an arbitrary function that consumes a Result Http.Error SearchResult and turns it into a Msg. In your original code, that function is just ReceivedResponse, the Msg constructor. When the Msg type is updated so that ReceivedResponse takes two arguments, the ReceivedResponse constructor function becomes a curried two-argument function, and ReceivedResponse "some query here" is a one-argument function that takes in a Result and returns a Msg.
Here's one way:
Add two integers to your model:
requestsSent : Int -- the number of requests made.
lastReceived : Int -- the latest request that you've processed.
Modify ReceivedResponse to have an Int as the first value:
| ReceivedResponse Int (Result Http.Error SearchResult)
Now, whenever you make a request, increment requestsSent by 1 in the model and "tag" the request by partially applying ReceivedResponse:
Http.send (ReceivedResponse model.requestsSent) request
In your update function, check if the Int in the ReceivedResponse is greater than lastReceived or not. If it is, process it, and set the value of lastReceived to this response's Int. If it isn't, discard it, because you've already processed a newer request.

how to create dynamic http body in elm

I am implementing the service where i have to create the dynamic http post request and my code is below.
postRequest: Int -> Http.Request
postRequest catId =
let
body =
"""{"categoryId:"""++catId++""","coupon":false,"domainId":1,"locations":[],"onlineMenu":false,"onlineOrder":false,"pageNo":1,"pageSize":10,"reservation":false,"searchText":"","subcategories":[]}"""
in
{ verb = "POST"
, headers =
[("Content-Type", "application/json")
]
, url = "http://xyz/businesses/list"
, body = Http.string body
}
but i am getting some error
how to concatenate the catId in the body and catId is integer type.
please anyone suggest what i have did wrong in the implementation.
As you declared catId as Int not String, so
(++) : String -> String -> String cannot not work on it.
You can use toString : a -> String before concatenating it with strings.
"categoryId:" ++ (toString catId)

NSJSONSerialization.JSONObjectWithData changes field type

I'm getting the following JSON response from the server:
{
"userId":"123456789",
"displayName":"display name"
}
When I use NSJSONSerialization.JSONObjectWithData and then prints the result NSDictionary I see in the console the following:
userId = 123456789
displayName = "display name"
Why do JSONObjectWithData changes the userId field type from String to a number?
It doesn't. The JSON deserialisation respects the data type and will maintain it. You can't tell the data type from a simple description log, you need to actually interrogate the class. The description log will quote some things if it makes more sense for the human reader, like spaces in the description, but it also omits quotes in some cases.
It doesn't.
Don't infer a variable type from its log representation, just test. Fire a Playground with this, for example:
let str = "{\"userId\":\"123456789\",\"displayName\":\"display name\"}"
if let data = str.dataUsingEncoding(NSUTF8StringEncoding),
jsonResult = try? NSJSONSerialization.JSONObjectWithData(data, options: []),
jsonObject = jsonResult as? [String:String],
id = jsonObject["userId"] {
print("User ID is " + id)
}