Is it possible to Http.send a list of Requests?

113 views
Skip to first unread message

mt-i

unread,
May 25, 2013, 11:36:26 PM5/25/13
to elm-d...@googlegroups.com
Hello all,

Thanks for all the great work on Elm, which is definitely looking like a very nice way of creating dynamic web pages in a functional fashion.

I've been looking into it more recently, and there are a few things I don't quite understand about the Http library. Mainly, I think I don't quite see why Http.send has type:

  Signal (Request a) -> Signal (Response String)

instead of just

  Request a -> Signal (Response String)

as you would expect a typical function with side effects to have (which would allow to sequence it with others using Kleisli composition, maybe? although maybe not due to the Signal-is-not-a-monad limitation?).

The question arose as I tried to modify the "Flickr search" example on the website to show multiple search results instead of just one. To do so, it seems natural to replace

  getOneFrom : Response String -> Request String,

(a function which takes the search results as a JSON string, extract the list of image references, and yields an HTTP GET request for the first image in the list) by

  getAllFrom : Response String -> [Request String]

which outputs a list of HTTP GET requests. But then, at some point, that pure function gets lifted into Signal, and hence I get a Signal [Request String], and I have no idea how to Http.send those requests, or more generally, how do deal with situations when multiple non-constant Http requests need to be made. Any clue?

Thanks a lot!

-- 
mt-i

John Mayer

unread,
May 26, 2013, 12:01:48 AM5/26/13
to elm-d...@googlegroups.com
I've been looking into it more recently, and there are a few things I don't quite understand about the Http library. Mainly, I think I don't quite see why Http.send has type:
  Signal (Request a) -> Signal (Response String)
instead of just
  Request a -> Signal (Response String)
as you would expect a typical function with side effects to have (which would allow to sequence it with others using Kleisli composition, maybe? although maybe not due to the Signal-is-not-a-monad limitation?).

Signals are not sufficiently expressive to use Kleisli composition; they are only Applicative Functors and not Monads. This is an intentional restriction. In this specific case, the point of HTTP.send is that you can fetch some new content each time an input field changes, as an example.

However, you do raise the valid point that there isn't really a way to tell from the types if side effects occur. For example, mapping a Signal String to lowercase characters looks "sorta no different" than HTTP.send, though it is really an "async promise" under the hood.

For your last question, I suspect you might need some clever use of foldp. Not quite sure what the use case is, perhaps there's a better way to solve it.

John


-- 
mt-i

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Jeff Smits

unread,
May 26, 2013, 7:44:53 AM5/26/13
to elm-d...@googlegroups.com, john.p....@gmail.com
I think a general solution would be to add an inverse of combine to Elm: uncombine : Signal [a] -> [Signal a]. Although combine is implemented in pure Elm, uncombine cannot be implemented in pure Elm (AFAIK). But I think it can be handy and I can't find a way in which it can jeopardise the Signal not being a Monad. 
Something similar that can be used right now is the following:

-- extract gives a list with a length of _amount_, with Signals of Just when the list in _s_ has a corresponding value and Signals of Nothing after that. 
extract : Int -> Signal [a] -> [Signal (Maybe a)]
extract amount s = if amount == 0 then [] else
                     (mHead <~ s) :: (extract (amount-1) <| (maybe [] id . mTail) <~ s)

mHead : [a] -> Maybe a
mHead l = case l of
            []   -> Nothing
            h::_ -> Just h

mTail : [a] -> Maybe [a]
mTail l = case l of
            []   -> Nothing
            _::t -> Just t

Does this help?

mt-i

unread,
May 26, 2013, 10:18:38 AM5/26/13
to elm-d...@googlegroups.com
On Sunday, May 26, 2013 1:01:48 PM UTC+9, John Mayer wrote:
Signals are not sufficiently expressive to use Kleisli composition; they are only Applicative Functors and not Monads. This is an intentional restriction. In this specific case, the point of HTTP.send is that you can fetch some new content each time an input field changes, as an example.

I see, thanks. While I did understand why Http.send returns a signal, it seemed strange to me that it would take one as input (as opposed to, say, printStrLn in Haskell having type String -> IO () and not IO String -> IO ()). But since Kleisli composition isn't possible, it makes sense: a function that returns "Signal b" cannot be lifted anymore, so it has to take "Signal a" as input for any parameter where one may want to pass it the contents of a signal at some point. Is that correct?
 
For your last question, I suspect you might need some clever use of foldp. Not quite sure what the use case is, perhaps there's a better way to solve it.

If you don't like the case of multiple images from Flickr, one can also think of the ZipCodes example:


which we modify to support a comma-separated list of zip codes instead of a single zip code as input. Many real-world examples are of that type, and it seems as if it's currently impossible to do with the Http library if you want to support arbitrary long lists of zip codes.

On Sunday, May 26, 2013 8:44:53 PM UTC+9, Jeff Smits wrote:
I think a general solution would be to add an inverse of combine to Elm: uncombine : Signal [a] -> [Signal a]. Although combine is implemented in pure Elm, uncombine cannot be implemented in pure Elm (AFAIK).

Indeed, that would solve the problem completely! If there is no theoretical obstacle to having such a function, it would be very helpful. Can I make it a feature wish?
 
But I think it can be handy and I can't find a way in which it can jeopardise the Signal not being a Monad. 
Something similar that can be used right now is the following:

-- extract gives a list with a length of _amount_, with Signals of Just when the list in _s_ has a corresponding value and Signals of Nothing after that. 
extract : Int -> Signal [a] -> [Signal (Maybe a)]
extract amount s = if amount == 0 then [] else
                     (mHead <~ s) :: (extract (amount-1) <| (maybe [] id . mTail) <~ s)

Does this help?

It does, thanks! While playing with the zip codes example, I had come up with something similar but more awkward and with quadratic complexity:

ithresponse : Int -> Signal (Response String)
ithresponse i = Http.sendGet . lift (maybe "" id . ith i) $ realInput

responses : [Signal (Response String)]
responses = map ithresponse [0..4]

where ith : Int -> [a] -> Maybe a is the obvious function.

In both cases, the number of Http.send calls is a compile-time constant, though, which isn't sufficient for some applications (though it is for what I had in mind).

-- 
mt-i

Jeff Smits

unread,
May 26, 2013, 10:32:39 AM5/26/13
to elm-d...@googlegroups.com
On Sunday, May 26, 2013 4:18:38 PM UTC+2, mt-i wrote:
I see, thanks. While I did understand why Http.send returns a signal, it seemed strange to me that it would take one as input (as opposed to, say, printStrLn in Haskell having type String -> IO () and not IO String -> IO ()). But since Kleisli composition isn't possible, it makes sense: a function that returns "Signal b" cannot be lifted anymore, so it has to take "Signal a" as input for any parameter where one may want to pass it the contents of a signal at some point. Is that correct?

Yes, I'm pretty sure that's the reason. But to be completely sure we would need to hear from Evan ;)

Indeed, that would solve the problem completely! If there is no theoretical obstacle to having such a function, it would be very helpful. Can I make it a feature wish?

Absolutely! Please read this post for instructions on how to submit a feature request.  

Evan Czaplicki

unread,
May 26, 2013, 10:53:05 AM5/26/13
to elm-d...@googlegroups.com
Yeah, the signals-of-signals solution is not going to work. I have a long post going into why signals-of-signals cannot be a thing that I want to post soon that hopefully will clarify this. I can post on the list right after this. I have no great attachment to the Http API, I and others have had problems with it for a while. A common question is "how do I send a bunch of requests at once?"

I think we should brainstorm exactly the features we want, and then think about the APIs that might work for that.

I can imagine two different routes that get closer to what we want, but I think I'd need a list of abilities and invariants to get this right.

send : Signal [Request a] -> Signal [Response String]
-- this seems like it'd be hard to match requests to results

There could maybe be an arrowized version that reifies the asynchronous parts?

-- not sure I am getting the syntax exactly right
requestTag : AsyncArrow String Element
requestTag tag = do proc
    request <- addUrl -< tag
    sizes <- send -< request
    sizeRequest <- pickSize -< sizes
    imageName <- send -< sizeRequests
    fittedImage 100 100 imageName

I think both of these have problems and I don't want to use the "do proc" notation that way, but hopefully the ideas push this discussion forward.

Laszlo Pandy

unread,
May 26, 2013, 1:08:02 PM5/26/13
to elm-d...@googlegroups.com
I just want to say that even if it were allowed in Elm for Http.send to create a new signal every time, this is not a good API.

Bacon.js has an AJAX API very similar to the proposed:
Http.send : Request a -> Signal (Response String)

Here, each Http request creates a signal, that will only ever produce one value (the response or failure).
Then you have to use flatMapLatest() to get the latest value of any of the streams.

You can see details in the diagram at the end of this page:

I immediately asked:
1. Why am I creating so many EventStreams if each one is only going to produce a single value?
2. What happens if you start another request before the first one ends (manual retry)? Does my code have to manually cancel the stream for it to be garbage collected?
3. Why do I have to understand flatMapLatest() to do Http?

Elm can do better.

Jeff Smits

unread,
May 27, 2013, 11:39:30 AM5/27/13
to elm-d...@googlegroups.com
I think send : Signal [Request a] -> Signal [Response String] would already be an improvement. But perhaps the whole API needs to be overhauled (I don't know, I've never used it). 
The more general uncombine : Signal [a] -> [Signal a] could also be used... Do you have anything against that function? It seems natural, given that there's a combine. 

Evan Czaplicki

unread,
May 27, 2013, 3:18:52 PM5/27/13
to elm-d...@googlegroups.com
If you can write uncombine in terms of other primitives it is fine, but I would be surprised if it was possible.

Jeff Smits

unread,
May 28, 2013, 4:42:12 AM5/28/13
to elm-discuss
I'm fairly sure you can't write uncombine in pure Elm. That's why I was asking actually... For the implementation of uncombine you would need to break the signal-is-not-a-monad rule (i.e. you need to implement it in natively in the runtime), but that doesn't leak. That is, the use of uncombine would still be safe I think. And it would result in the same situations as send : Signal [Request a] -> Signal [Response String]

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/iUdZewHmYoo/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Juha Paananen

unread,
Aug 5, 2013, 2:12:29 PM8/5/13
to elm-d...@googlegroups.com
Just stumbled upon this. Don't know if it's relevant but

1) EventStream (like Signal) is an abstraction that suits 0, 1 and many values. Why have a separate abstraction for a thing that produces just 1 value? A plain value won't do because the result is asynchronous and may result to an Error.
2) The flatMapLatest combinator (as per readme) only cares about the latest created stream. It does indeed keep track of all created streams and unsubscribes from them automatically. That's the power of FRP.
3) You don't. But if you want to take advantage of the full power of FRP (or any paradigm library etc), you should learn to use the basic building blocks. In Bacon.js they are the combinators map,filter,merge,scan,flatMap etc.

If you take a look at bacon-jquery-bindings there's an easier API for AJAX.

    ajax :: EventStream Request -> EventStream Response

It, of course relies on flatMapLatest internally.

Happy hacking! BTW I'm going to learn Elm today.

-juha-
Reply all
Reply to author
Forward
0 new messages