Lifting Capture param requirements when getting Text-ified Route

28 views
Skip to first unread message

Christopher Allen

unread,
Oct 16, 2016, 7:52:32 PM10/16/16
to haskell-servant
Code for context:

-- renderURI :: URI -> App Text

type LinkableRoute a = (IsElem a Routes, HasLink a)
type FinalLink a = (LinkableRoute a, MkLink a ~ URI)

routeToText :: (FinalLink a)
            => Proxy a
            -> App Text
routeToText = renderURI . routeToURI

-- | Get link representation for specific endpoint on the site.

routeToURI :: (LinkableRoute a) => Proxy a -> MkLink a
routeToURI = safeLink (Proxy :: Proxy Routes)


The problem is routes that have a capture param as a result, routeToText only takes parameter-less routes.

routeToURI works fine, but you have to apply params after that, _then_ render. As a result, it doesn't really compose nicely.

Is there a way to clean this up that doesn't involve making a hairy poly-arg typeclass or something? I feel like I'm missing a Servant component or helper function intended to take care of this, but all I can see is the type family expanding into the arity and immediately requiring the arguments.

Here's the code I'm trying to make nicer:

  postUri <- renderURI $ routeToURI proxyGetPostR (articleSlug a)

Ideally it would be:

  postUri <- routeToText proxyGetPostR (articleSlug a)

Or:

  postUri <- (routeToText proxyGetPostR) (articleSlug a)

But that gets the type error:

    Couldn't match type ‘Slug -> URI’ with ‘URI’

Currently I have to explicitly stage out routeToURI, applying the Capture params, and _then_ render it to text. This wasn't a problem in Yesod. What am I missing?

Julian Arni

unread,
Oct 17, 2016, 6:40:40 AM10/17/16
to Christopher Allen, haskell-servant

Hi Chris,

I don't think you're missing anything in 'servant' itself. But perhaps Oleg
Kiselyov's 'mcomp' [0] is of help. The new 'routeToText' would just be:

> renderURI `mcomp` routeToURI

I don't think anyone has packaged 'mcomp' yet. For your use case there is, I
think, only one base case (URI), so it'd in all be something like:

> class MCompose f1 f2 cp gresult result | f1 f2 cp gresult -> result, f2->cp
> where
> mcomp:: (f1->f2) -> (cp->gresult) -> result
>
> instance (MCompose f1 f2 cp gresult result) =>
> MCompose a (f1->f2) cp gresult (a->result) where
> mcomp f g = \a -> mcomp (f a) g
>
> instance MCompose a URI URI c (a->c) where
> mcomp f g = g . f

I hope this helps!

[0] http://okmij.org/ftp/Haskell/polyvar-comp.lhs
> --
> You received this message because you are subscribed to the Google Groups "haskell-servant" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to haskell-serva...@googlegroups.com.
> To post to this group, send email to haskell...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/haskell-servant/c9903901-be16-4b45-8bba-fd4f8c786547%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.


--
Julian K. Arni
Haskell Consultant, Turing Jump
https://turingjump.com
signature.asc

Jonathan Lange

unread,
Oct 18, 2016, 5:39:42 AM10/18/16
to Julian Arni, Christopher Allen, haskell-servant
On Mon, 17 Oct 2016 at 11:40 Julian Arni <jka...@turingjump.com> wrote:

Hi Chris,

   I don't think you're missing anything in 'servant' itself. But perhaps Oleg
Kiselyov's 'mcomp' [0] is of help. The new 'routeToText' would just be:

>   renderURI `mcomp` routeToURI

I don't think anyone has packaged 'mcomp' yet. For your use case there is, I
think, only one base case (URI), so it'd in all be something like:



Is there something we can do to make this more discoverable / easier to use for newcomers to Servant? e.g. exporting a wrapper? After all, it's a fairly common thing to want, and downloading something from Oleg's website is perhaps a little extreme.

jml

Alp Mestanogullari

unread,
Oct 18, 2016, 6:26:07 AM10/18/16
to Jonathan Lange, Julian Arni, Christopher Allen, haskell-servant
If you come up with a wrapper that makes things easier for a servant user, you can definitely make it a PR. For now, it might be enough for someone to package up mcomp and put it on hackage, while on the servant side we can either just point to it in the Link docs with an accompanying example, or even re-export it from there.

--
You received this message because you are subscribed to the Google Groups "haskell-servant" group.
To unsubscribe from this group and stop receiving emails from it, send an email to haskell-servant+unsubscribe@googlegroups.com.
To post to this group, send email to haskell-servant@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/haskell-servant/CAHZ8tnY%3D47YSOoPmSTkjqYYs53wjnR%3DWY_U231qDOvTYWDP8fA%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.



--
Alp Mestanogullari

Christopher Allen

unread,
Oct 18, 2016, 12:02:20 PM10/18/16
to Alp Mestanogullari, Jonathan Lange, Julian Arni, haskell-servant
I still haven't gotten the mcomp'd version to type-check.
>> email to haskell-serva...@googlegroups.com.
>> To post to this group, send email to haskell...@googlegroups.com.
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/haskell-servant/CAHZ8tnY%3D47YSOoPmSTkjqYYs53wjnR%3DWY_U231qDOvTYWDP8fA%40mail.gmail.com.
>>
>> For more options, visit https://groups.google.com/d/optout.
>
>
>
>
> --
> Alp Mestanogullari



--
Chris Allen
Currently working on http://haskellbook.com

Jonathan Lange

unread,
Nov 4, 2016, 6:39:11 AM11/4/16
to Christopher Allen, Alp Mestanogullari, Julian Arni, haskell-servant
I saw on Twitter that Christopher got this resolved with Julian's help. I'd be quite keen to learn how, if either is willing to share.

Thanks,
jml

Jonathan Lange

unread,
Nov 4, 2016, 6:48:33 AM11/4/16
to Julian Arni, haskell-servant
Thank you! 

On Fri, 4 Nov 2016 at 10:44 Julian Arni <jka...@turingjump.com> wrote:
Here are the two messages exchanged outside of the haskell-servant usergroup. My fault for accidentally not CCing the group.

---------- Forwarded message ----------
From: Christopher Allen <c...@bitemyapp.com>
Date: Thu, Oct 20, 2016 at 9:40 PM
Subject: Re: Lifting Capture param requirements when getting Text-ified Route
To: Julian Arni <jka...@turingjump.com>


Finally got it working, the difference was my version wasn't static -
depends on the App monad.

routeToText :: ( IsElem a Routes
               , HasLink a
               , MCompose () (MkLink a) URI (App Text) (() -> w)
               )
            => Proxy a
            -> w
routeToText p = go ()
  where go = (\() -> routeToURI p) `mcomp` renderURI


This now compiles:

articleListing :: Article -> App Html
articleListing a@Article{..} = do

  postUri <- routeToText proxyGetPostR (articleSlug a)

Thank you very much!

On Tue, Oct 18, 2016 at 11:41 AM, Julian Arni <jka...@turingjump.com> wrote:
> Probably because I got the composition order wrong (mcomp flips .). There's
> also a little dance with the case of no arguments that I hadn't thought about.
>
> Here's some code, this time actually tested:
>
>> {-# LANGUAGE DataKinds #-}
>> {-# LANGUAGE MultiParamTypeClasses #-}
>> {-# LANGUAGE FunctionalDependencies #-}
>> {-# LANGUAGE FlexibleInstances #-}
>> {-# LANGUAGE FlexibleContexts #-}
>> {-# LANGUAGE GADTs #-}
>> {-# LANGUAGE TypeOperators #-}
>> {-# LANGUAGE UndecidableInstances #-}
>> import Servant
>>
>> routeToURI :: (IsElem a MyAPI, HasLink a) => Proxy a -> MkLink a
>> routeToURI = safeLink (Proxy :: Proxy MyAPI)
>>
>>
>> type Ep1 =  "static" :> Get '[JSON] Int
>> type Ep2 = Capture "" Int :> Ep1
>> type Ep3 = Capture "" String :> Capture "" Int :> Ep1
>> type MyAPI = Ep1 :<|> Ep2 :<|> Ep3
>>
>> routeToText ::
>>   (IsElem a MyAPI, HasLink a, MCompose () (MkLink a) URI String (() -> w))
>>             => Proxy a
>>             -> w
>> routeToText p = go ()
>>   where go = (\() -> routeToURI p) `mcomp` renderURI
>>
>> renderURI :: URI -> String
>> renderURI = show

>>
>>
>> class MCompose f1 f2 cp gresult result | f1 f2 cp gresult -> result, f2->cp where
>>  mcomp:: (f1->f2) -> (cp->gresult) -> result
>>
>> instance (MCompose f1 f2 cp gresult result) =>
>>          MCompose a (f1->f2) cp gresult (a->result) where
>>   mcomp f g = \a -> mcomp (f a) g
>>
>> instance MCompose a URI URI c (a->c) where
>>   mcomp f g = g . f
>>
>> main :: IO ()
>> main = do
>>   putStrLn $ routeToText (Proxy :: Proxy Ep1)
>>   putStrLn $ routeToText (Proxy :: Proxy Ep2) 5
>>   putStrLn $ routeToText (Proxy :: Proxy Ep3) "neat" 5

>> For more options, visit https://groups.google.com/d/optout.
>
> --

> Julian K. Arni
> Haskell Consultant, Turing Jump
> https://turingjump.com



--
Chris Allen
Currently working on http://haskellbook.com



--
Julian K. Arni, Haskell Consultant
Turing Jump, https://turingjump.com
Reply all
Reply to author
Forward
0 new messages