Getting type signatures for a function in elm - type-inference

I'm using elm 0.18.
Let's say I have a function that strings together a bunch of stuff that I threw together in a hurry. It works, but I'm not sure what it's type signature is, and I'd like elm to tell me (or hint for me) that type signature.
For example, I use graphql and have a function that takes a graphql string, a decoder (which also doesn't have a type signature), and a Cmd Msg, and runs it through HttpBuilder.
graphQLPost graphiql decoder msg =
HttpBuilder.post (url ++ "api")
|> HttpBuilder.withStringBody "text/plain" graphiql
|> HttpBuilder.withExpect (Http.expectJson decoder)
|> HttpBuilder.send msg
This works, though I don't know why. I tried fitting it with the type signature graphQLPost : String -> Json.Decode.Decoder -> Cmd Msg, but I get an error.
Figuring out this type signature is not as important to me as finding a way to induce them through elm. Is there a command that I can enter into elm-repl or something that will tell me the signature?

Elm REPL will do this for you:
> import Http
> import HttpBuilder
> type Msg = Msg
> url = "..."
"..." : String
> graphQLPost graphiql decoder msg = \
| HttpBuilder.post (url ++ "api") \
| |> HttpBuilder.withStringBody "text/plain" graphiql \
| |> HttpBuilder.withExpect (Http.expectJson decoder) \
| |> HttpBuilder.send msg
<function>
: String
-> Json.Decode.Decoder a
-> (Result.Result Http.Error a -> msg)
-> Platform.Cmd.Cmd msg
When you write a function and hit <Enter>, it shows you the signature. In this case the signature is:
graphQLPost : String
-> Json.Decode.Decoder a
-> (Result.Result Http.Error a -> msg)
-> Platform.Cmd.Cmd msg

Running elm-make with the --warn option will cause the compiler to suggest that you include a type annotation on functions that don't have one, and it will provide one for you to copy and paste in.
Also, some editor integrations, such as the Visual Studio Code language extension for Elm, will display these kinds of warnings as a hint icon that you can click to add the missing type annotation automatically. You can set a keyboard shortcut for this to do it without your hands leaving the keyboard.

Related

How do you iterate a List (Maybe a)

I have the following graphQL result:
[Just { details = Just "Engine failure at 33 seconds and loss of
vehicle", launch_year = Just "2006", links = Just { article_link =
Just
"https://www.space.com/2196-spacex-inaugural-falcon-1-rocket-lost-launch.html"
}, mission_name = Just "FalconSat" }]
Based on the following types:
type alias Launch =
{ mission_name : Maybe String
, details : Maybe String
, launch_year : Maybe String
, links : Maybe LaunchLinks
}
type alias Launches =
Maybe (List (Maybe Launch))
type alias LaunchLinks =
{ article_link : Maybe String
}
I want to List.map through and display the results in unordered list. I started with this:
renderLaunch : Launches -> Html Msg
renderLaunch launches =
div [] <|
case launches of
Nothing ->
[ text "Nothing here" ]
Just launch ->
launch
|> List.map (\x -> x)
|> ul []
But I keep getting this error:
This function cannot handle the argument sent through the (|>) pipe:
141| launch 142| |> List.map (\x
-> x) 143| |> ul []
^^^^^ The argument is:
List (Maybe Launch)
But (|>) is piping it a function that expects:
List (Html msg)
The problem is that the Just launch case needs to result in a List (Html msg) but the code results in a different type being returned.
When you are using List.map (\x -> x), it is essentially a no-op. You are iterating over a List (Maybe Launch) and returning the same thing. I'd recommend creating another function that takes a Maybe Launch value and use that as your mapping function. For example:
displayLaunch : Maybe Launch -> Html Msg
displayLaunch launch =
case launch of
Nothing -> text "No launch"
Just l -> text (Debug.toString l)
Now you can plug that into your mapping function:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
But, whoops! Now you get a new error indicating:
The 2nd branch is:
Html Msg
But all the previous branches result in:
List (Html msg)
The problem here is that we are now returning a ul from the Just launch branch and we need to return a list of html. You can use List.singleton to create a list with just one item:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
|> List.singleton

Retrieving a DOM value from Elm ports

My elm app uses an auto scrolling function, which gets the Y position of an element and uses Dom.Scroll.toY to scroll there.
Two do this, I set up two ports; a subscription and sender.
ports.elm
port setYofElementById : Maybe String -> Cmd msg
port getYofElementById : (Value -> msg) -> Sub msg
index.html
app.ports.setYofElementById.subscribe(function(id) {
var element = document.getElementById(id);
var rect = element.getBoundingClientRect();
app.ports.getYofElementById.send({"number": rect.top});
})
The listener is a subscription
subscriptions : Model -> Sub Msg
subscriptions model =
Ports.getYofElementById getYofElementById
getYofElementById : Decode.Value -> Msg
getYofElementById value =
let
result =
Decode.decodeValue bSimpleIntValueDecoder value
in
case result of
Ok simpleIntValue ->
SetSelectedElementYPosition (Just simpleIntValue.number)
Err id ->
SetSelectedElementYPosition Nothing
SetSelectedElementYPosition just sets the model.
Now, the action that executes this does two things: call Port.setYofElementById, then scrolls to the Y value in the model, assuming that it has already been set.
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollToY idString ->
model
=> Cmd.batch
[ Ports.setYofElementById (Just idString)
, Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" model.selectedElementYPosition
]
However, this doesn't happen sequentially. When the action first fires, nothing happens. If I fire it again, it scrolls to the location called for in the first action. So it seems like it is calling Dom.Scroll.toY before the value is set.
Is there a way to force the Cmds in ScrollToY to happen in sequence? Or is there a better way to do this in general?
You can get the Cmds to execute in sequence by making the second, the one that does the Dom.Scroll.toY, happen as a response to the first, the one that does the setYofElementById. The following update function accomplishes this:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ScrollToY idString ->
(model, Ports.setYofElementById idString)
SetSelectedElementYPosition (Just newY) ->
(model, Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" newY)
SetSelectedElementYPosition Nothing ->
(model, Cmd.none)
NoOp ->
(model, Cmd.none)
With the Cmds correctly sequenced, you will need to make sure that the newY argument to Dom.Scroll.toY is in the correct frame of reference to get the effect that you want.
I finally got this to work by tacking the action for Task.attempt (always NoOp) <| Dom.Scroll.toY "ul" model.selectedElementYPosition onto the action called by the subscription, not the action. That's the key.
With ports, the subscribe and send actions follow completely different pathways, so anything that reacts to a send from js to elm is not going to be referenced in the actions that go from elm to js.
In this case, since SetSelectedElementYPosition is being called from the subscription, you have to set the update there:
SetSelectedElementYPosition idString ->
({model | selectedElementYPosition = number }, Cmd.none)
|> andThen update GoToSelectedElementYPosition

HTTP request without request in Elm 0.18

I want to do something very un-functional and make an HTTP request in elm without processing any kind of response. Basically something like this:
testView : Html Msg
testView =
div [] [
button [onClick TestAction] [text "Test Action"]
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
...
TestAction ->
( model, testActionCmd )
...
import Http
import HttpBuilder exposing (..)
...
testActionCmd : Cmd Msg
testActionCmd =
( "http://localhost:4000/fakeurl" )
|> get -- this is a side effect; unrelated to the Msg below
Cmd.none -- this is what I want to return
Is there a way to do something like this in Elm?
In short, no, you won't be able to do that (not without writing your own effect manager or using ports).
The "problem" is that the Http module allows you to create a Task which you then need to convert into a Cmd to perform the task. But to go from a Task to a Cmd you need to provide a Msg. See http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Task
So what you'll need to do is create one of those Noop messages.

How do I implement and return a Cmd Msg?

How do I implement and return a Cmd Msg?
For example, the following line generates a Cmd Msg:
Http.send msg request
It's used in the following function:
tryRegister : Form -> (Result Http.Error JsonProfile -> msg) -> Cmd msg
tryRegister form msg =
let
registerUrl =
"http://localhost:5000/register"
body =
encode form |> Http.jsonBody
request =
Http.post registerUrl body decoder
in
Http.send msg request
I'm trying to hand code a similar function within my TestAPI:
tryRegister : Form -> (Result Http.Error JsonProfile -> msg) -> Cmd msg
tryRegister form msg =
Cmd.none
The above code compiles. However, it's not clear to me how to implement a function that returns a Cmd Msg other than Cmd.none.
Appendix:
type Msg
=
...
| Submit
| Response (Result Http.Error JsonProfile)
update : Msg -> Form -> ( Form, Cmd Msg )
update msg model =
case msg of
...
Submit ->
( model, runtime.tryRegister model Response )
Source code on GitHub.
Edit
The original answer suggested mapping over Cmd.none, which compiles and may potentially be useful when mocking out functions for testing, but if you are actually trying to force another update cycle in The Elm Architecture, you will need to convert to a Task, as outlined in the send function described here.
send : msg -> Cmd msg
send msg =
Task.succeed msg
|> Task.perform identity
As #SwiftsNamesake mentioned above, in most cases this is not necessary, and the entire blog post on the subject is worth a read.
Original Answer
You can use Cmd.map over Cmd.none to change it to any Cmd:
Cmd.map (always Submit) Cmd.none

Basic Chat using elm-native-ui

I'm creating a basic native chat app with elm-native-ui.
I can open or close the chat by clicking on a button that change the chatOpen boolean
activeChannelView =
case chatOpen of
True ->
Maybe.map (\a -> chatView users a messages) activeChannel ? viewEmpty
False ->
Maybe.map (cardView users) activeChannel ? viewEmpty
on the False side, it’s working well but on the True side it says that viewEmpty needs to be a (String -> Node Msg) instead of being (Node Msg) but the left side (chatView user a messages) returns Node Msg
Check the error below
The right side of (?) is causing a type mismatch.
Maybe.map (\a -> chatView users a messages) activeChannel ? viewEmpty
^^^^^^^^^
(?) is expecting the right side to be a:
String -> Node Msg
But the right side is:
Node Msg
I suppose it means that:
if activeChannel is (Just a), True returns (String -> Node Msg)
How is it possible to have this error since chatView is defined like that:
chatView : List User -> Request -> List String -> String -> Node Msg
chatView takes four parameters and you are passing only three.