Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simultaneous match and map accept #21

Open
Zankoku-Okuno opened this issue Jun 1, 2017 · 5 comments
Open

Simultaneous match and map accept #21

Zankoku-Okuno opened this issue Jun 1, 2017 · 5 comments

Comments

@Zankoku-Okuno
Copy link
Contributor

In the interests of self describing media, I find myself often implementing these:

mapDescribe :: Accept a => [(a, b)] -> ByteString -> Maybe (a, b)
mapDescribe options header = mapAccept (map (\(a, b) -> (a, (a, b))) options) header

mapQualityDescribe :: Accept a => [(Quality a, b)] -> [Quality a] -> Maybe (a, b)
mapQualityDescribe options header = mapQuality (map (\(a, b) -> (a, (a, b))) options) header

It seems like this sort of function belongs to this library rather than users' code since a) it's quite useful for self-describing media, and b) an internal implementation can do it with fewer thunks. I'm not really sure about the names I've chosen here, though.

@zmthy
Copy link
Owner

zmthy commented Jun 14, 2017

I'm not sure about this, because it seems simple enough to write a transformation over the input dictionary.

mapAccept . map (join (<$))

I'm prepared to buy the argument that we would be better off not constructing a larger data structure as the input if this is common enough. Perhaps we could generalise this to the second element of each pair in the mapping being a function that takes the exact media type that matched, so the function can specialise based on a potentially more specific media type than the first element of the pair? I'm not sure if that's desirable, though.

What's the specific context that you find self-describing media useful in? I would have thought it would be better to only care about the media type at the exact point where mapAccept is used, and then treat the result as an opaque representation of the resource that will only make sense to the client.

@Zankoku-Okuno
Copy link
Contributor Author

It's not that I need to know the media type in order to compute the content, it's just that I can't imagine when I would ever perform content negotiation in order to determine either the type or the content, but not both.

Long story short, I use it every time I want to set the Content-Type header on an HTTP response, which is every response.

It's a bit strange getting asked where I find self-describing media useful; it's just part of the REST architectural style (now that I google it though, I see the term is actually "self-descriptive message"). Like, this example is an "action item" resource from my latest site, but it could just as easily be any other resource on the site, or the web.

actionItem_R :: Db -> Pk ActionItem -> Request -> IO Response
actionItem_R db pk req = do
    item <- ...
    (ctype, body) <- mapQualityDescribe
                [ ("text/html", html_F item)
                , ("application/json", json_F item)
                ] (acceptMedia req)
    pure $ Response
        { status = Http.status200
        , responseContentType = ctype
        , responseBody = body }
    where
    html_F item = renderBS $ ...
    json_F item = encode $ object [ ... ]

(Huh, I was re-reading your post to make sure I answered everything, and then I noticed that I agree with every sentiment in your last paragraph. Then I re-read my original post and realized just how poorly I described what I'm doing... whoops!)

And the more I think about it, the more I think map*Describe is a terrible name, but I still don't have anything better.

@zmthy
Copy link
Owner

zmthy commented Jun 14, 2017

I was re-reading your post to make sure I answered everything, and then I noticed that I agree with every sentiment in your last paragraph.

This is my fault, the comment was really two different comments that I joined together.

Your example seems like a reasonable justification. I was thinking about how I do this in the snap-accept package, where the second element of each pair is a Snap monad value with the rest of the response, where the library automatically sets the matched media type in the response state before running that monad computation. Turns out I'm doing basically the same thing (where withHeader sets the response Content-Type:

mapAccept . map (join $ fmap . withHeader . fst)

That's probably a good enough reason to implement this here. Maybe mapAcceptWithKey?

@Zankoku-Okuno
Copy link
Contributor Author

That name sounds decent. I knew it was going to involve a "with", but I guess "key" was just in a blind spot for me.

@zmthy
Copy link
Owner

zmthy commented Jun 18, 2017

The problem is that the functions are abstract enough that there's not a great name for the key (other than the type class we use to identify them, which is already in the function name). Maybe mapAcceptPaired is better?

Alternatively, the core functions could return the pair, and then if you don't care about the matched type you can call snd on the result. I might take a look at how the functions are used in some of the clients of this library on Hackage and see if that would be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants