Lifting Capture param requirements when getting Text-ified Route

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Lifting Capture param requirements when getting Text-ified Route

Christopher Allen
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?

--
Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Julian Arni-4

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


On Sun, Oct 16, 2016 at 04:52:32PM -0700, Christopher Allen wrote:

> 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 K. Arni
Haskell Consultant, Turing Jump
https://turingjump.com

--






signature.asc (484 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Jonathan Lange-2
On Mon, 17 Oct 2016 at 11:40 Julian Arni <[hidden email]> 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

--
Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Alp Mestanogullari
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.

On Tue, Oct 18, 2016 at 11:39 AM, Jonathan Lange <[hidden email]> wrote:
On Mon, 17 Oct 2016 at 11:40 Julian Arni <[hidden email]> 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

--
Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Christopher Allen
I still haven't gotten the mcomp'd version to type-check.

On Tue, Oct 18, 2016 at 5:25 AM, Alp Mestanogullari <[hidden email]> wrote:

> 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.
>
> On Tue, Oct 18, 2016 at 11:39 AM, Jonathan Lange <[hidden email]> wrote:
>>
>> On Mon, 17 Oct 2016 at 11:40 Julian Arni <[hidden email]> 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
>>
>> --
>>
>> "haskell-servant" group.
>>
>> email to [hidden email].
>>
>>
>> https://groups.google.com/d/msgid/haskell-servant/CAHZ8tnY%3D47YSOoPmSTkjqYYs53wjnR%3DWY_U231qDOvTYWDP8fA%40mail.gmail.com.
>>
>>
>
>
>
>
> --
> Alp Mestanogullari



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

--





Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Jonathan Lange-2
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

On Tue, 18 Oct 2016 at 17:02 Christopher Allen <[hidden email]> wrote:
I still haven't gotten the mcomp'd version to type-check.

On Tue, Oct 18, 2016 at 5:25 AM, Alp Mestanogullari <[hidden email]> wrote:
> 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.
>
> On Tue, Oct 18, 2016 at 11:39 AM, Jonathan Lange <[hidden email]> wrote:
>>
>> On Mon, 17 Oct 2016 at 11:40 Julian Arni <[hidden email]> 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
>>
>> --
>> >> "haskell-servant" group.
>> >> email to [hidden email].
>> >> >> https://groups.google.com/d/msgid/haskell-servant/CAHZ8tnY%3D47YSOoPmSTkjqYYs53wjnR%3DWY_U231qDOvTYWDP8fA%40mail.gmail.com.
>>
>> >
>
>
>
> --
> Alp Mestanogullari



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

--

--
Reply | Threaded
Open this post in threaded view
|

Re: Lifting Capture param requirements when getting Text-ified Route

Jonathan Lange
In reply to this post by Christopher Allen
Thank you! 

On Fri, 4 Nov 2016 at 10:44 Julian Arni <[hidden email]> 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 <[hidden email]>
Date: Thu, Oct 20, 2016 at 9:40 PM
Subject: Re: Lifting Capture param requirements when getting Text-ified Route
To: Julian Arni <[hidden email]>


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 <[hidden email]> 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

>
>
>
> On Tue, Oct 18, 2016 at 11:02:19AM -0500, Christopher Allen wrote:
>> I still haven't gotten the mcomp'd version to type-check.
>>
>> On Tue, Oct 18, 2016 at 5:25 AM, Alp Mestanogullari <[hidden email]> wrote:
>> > 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.
>> >
>> > On Tue, Oct 18, 2016 at 11:39 AM, Jonathan Lange <[hidden email]> wrote:
>> >>
>> >> On Mon, 17 Oct 2016 at 11:40 Julian Arni <[hidden email]> 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
>> >>
>> >> --
>> >> >> >> "haskell-servant" group.
>> >> >> >> email to [hidden email].
>> >> >> >> >> >> https://groups.google.com/d/msgid/haskell-servant/CAHZ8tnY%3D47YSOoPmSTkjqYYs53wjnR%3DWY_U231qDOvTYWDP8fA%40mail.gmail.com.
>> >>
>> >> >> >
>> >
>> >
>> >
>> > --
>> > Alp Mestanogullari
>>
>>
>>
>> --
>> Chris Allen
>> Currently working on http://haskellbook.com
>>
>> --
>> >> >> >> >> >
> --
> 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

--