-
Notifications
You must be signed in to change notification settings - Fork 21
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
Do you want a SelectiveDo proposal? #40
Comments
Personally, I'd love to have the Perhaps, it's possible to implement Even writing a good proposal would be an interesting challenge because there are still a lot of blank spots. For example, using multiway selective functors may be a better approach for If you are interested in trying to write a proposal despite the low chances of success, then I'm happy to help! |
A GHC plugin sounds like a great idea. I've never done that, so I'm not sure, but maybe at some point I'll look into that. If it works and is useful, a proposal might be the next step.
That's interesting. I would have thought that you can already use multiway selectives with
|
This is not exactly accurate. The data T a b = T0 | T1 a | T2 a b
getT :: Selective f => f (T a b)
x :: Selective f => f (() -> Maybe a)
y :: Selective f => f (a -> Maybe a)
z :: Selective f => f (a -> b -> Maybe a)
example :: Selective f => f (Maybe a)
example = do
t <- getT
-- These RHS expressions are OK because they don't inspect values
case t of
T0 -> x <*> pure ()
T1 a -> y <*> pure a
T2 a b -> z <*> pure a <*> pure b With multiway selective functors we can desugar it as follows: data Tag a b c where
Tag0 :: Tag a b ()
Tag1 :: Tag a b a
Tag2 :: Tag a b (a, b)
instance Enumerable (Tag a b) where
enumerate = [Some Tag0, Some Tag1, Some Tag2]
encode :: T a b -> Sigma (Tag a b)
encode = \case
T0 -> Sigma Tag0 ()
T1 a -> Sigma Tag1 a
T2 a b -> Sigma Tag2 (a, b)
example :: forall a f. Selective f => f (Maybe a)
example = match (encode <$> getT) handle
where
handle :: forall x b. Tag a b x -> f (x -> Maybe a)
handle = \case
Tag0 -> x
Tag1 -> y
Tag2 -> uncurry <$> z Here the type |
Ok, fair enough, the situation is more comfortable than I previously thought. So you could e.g. bind on a |
I think you might get some mileage out of |
|
@kozross I do want to experiment with |
I'm interested in helping implement this feature. What are the next steps? Has it been decided on whether to use RebindableSyntax, QualifiedDo, TemplateHaskell, or a plugin for this? |
@rashadg1030 It's still in (before?) the experimentation phase. I think you can help by:
|
@turion Thank you for your response. First let me describe my use case. It's related to my comment and Matthew Pickering's response here. This is the problem I'm trying to solve Problem: I've been working on a web framework called Okapi which is essentially a monadic EDSL for parsing web requests and returning web responses. I use it for creating servers. A simple example looks like this. {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Main where
import Control.Applicative ((<|>))
import Data.Aeson (ToJSON)
import Data.Text
import GHC.Generics (Generic)
import Okapi
main :: IO ()
main = runOkapi id 3000 calc
type Okapi a = OkapiT IO a
calc :: Okapi Response
calc = do
get
pathSeg "calc"
addOp <|> subOp <|> mulOp <|> divOp
addOp :: Okapi Response
addOp = do
pathSeg "add"
(x, y) <- getArgs
okJSON [] $ x + y
subOp :: Okapi Response
subOp = do
pathSeg "sub" <|> pathSeg "minus"
(x, y) <- getArgs
okJSON [] $ x - y
mulOp :: Okapi Response
mulOp = do
pathSeg "mul"
(x, y) <- getArgs
okJSON [] $ x * y
data DivResult = DivResult
{ answer :: Int,
remainder :: Int
}
deriving (Eq, Show, Generic, ToJSON)
divOp :: Okapi Response
divOp = do
pathSeg "div"
(x, y) <- getArgs
guard403 [] "Forbidden" (y == 0)
okJSON [] $ DivResult {answer = x `div` y, remainder = x `mod` y}
getArgs :: Okapi (Int, Int)
getArgs = getArgsPath <|> getArgsQueryParams
where
getArgsPath :: Okapi (Int, Int)
getArgsPath = do
x <- pathParam
y <- pathParam
pure (x, y)
getArgsQueryParams :: Okapi (Int, Int)
getArgsQueryParams = do
x <- queryParam "x"
y <- queryParam "y"
pure (x, y) The
The issue with this monadic EDSL approach compared to the approaches that other Haskell web frameworks use is the lack of information about the request parsers. This means Okapi can't have typed routes or openAPI doc generation like Servant. Unless there was a way to inspect the functions and generate an "AST" composed of Okapi's primitives like addRoute1 = [route|calc/add/:Int/:Int|]
addRoute2 = [route|calc/add?x:Int?y:Int|]
subRoute1 = [route|calc/sub/:Int/:Int|]
subRoute2 = [route|calc/sub?x:Int?y:Int|]
mulRoute1 = [route|calc/mul/:Int/:Int|]
mulRoute2 = [route|calc/mul?x:Int?y:Int|]
divRoute1 = [route|calc/div/:Int/:Int|]
divRoute2 = [route|calc/div?x:Int?y:Int|] The idea is that with these quasiquotes, Okapi would be able to check if the routes actually exist within the user's parser definitions and then generate a function that the user can use in their HTML templates or whatever else. Anyway, this all starts with being able to inspect the body of a monadic parser at compile time. My use case is pretty niche (an unknown web framework), but I could see this also being useful for something like I could push all that info to the type-level, but Servant already does this. I could do something like Yesod or Happstack, but that increases the complexity that the end user is faced with, and it's already been done so no use in me doing the same thing. I could also do something like If only I could inspect the RHS of the definitions at compile time and inspect what primitives they use, I'd be able to do all sorts of cool things! Even if it was Core, I'd be happy with that. Here was Matthew Pickering's response: Well I am not sure that you can achieve what you want because what if someone wrote a request handler which was something like: foo :: Okapi Response
foo = do
pathSeg "foo"
x <- getArgs
pathSeq x
... Then the list of acceptable paths isn't statically known because it depends on the argument of a request. That was the comment and Matthew Pickering's response. Here's a short summary of what I'm trying to accomplish: I'm working on an easy to use framework that allows the user to parse web requests and return responses. I'm using Monad because it allows data dependent effects and the end user can use the intuitive I've been lead to Selective Functors, which seem to be the key to having a web framework which allows the user to define their web requests in a nice notation (if there is some sort of do-notation), while still retaining enough static information to have type safe URLs, and generate OpenAPI documentation. This would be a great use case of Selective in my opinion, if it's possible. Perhaps @turion or @snowleopard can provide some insight into whether or not Selective Functors will actually help get the interface that I want and still have enough static information to analyze the web request parsers. If so, I'm very interested in working on a plugin, or anything else that would allow end users of the framework to use do-notation. EDIT: newtype OkapiT m a = OkapiT {unOkapiT :: ExceptT.ExceptT Failure (StateT.StateT State m) a}
deriving newtype
( Except.MonadError Failure,
State.MonadState State
) Judging by the discussion in #12 this will probably be difficult or impossible to do with Selective. Not sure. Any thoughts? |
I'd guess that this can be done with selectives, and that you'd profit from a ghc plugin that desugars do notation into selectives, but I'm not sure. You should be able to compose some specific selective functors. #12 is about the most general case, which is not trivial, but if you read the whole thread you'll notice that we found already 2 possibilities of composing selectives, so you can have a look which one is the right one for you. |
Awesome. Thank you for your input. I think it's worth it for me to go down this rabbit hole further and see what happens. Now, about the plugin. I remember reading a random comment on reddit or something that said GHC plugins can only manipulate GHC Core, and not Haskell syntax. Maybe I'm not remembering what I read correctly or I misunderstood. If I were to implement a GHC plugin and I've never done it before, where's a good place to start? Before I get started on this though, I should probably translate my framework's monadic interface into a selective one, and see how it works. |
Sorry, I've never done that myself and don't know where to start. I'd imagine that Matthew Pickering & friends have a lot of good talks & tutorials on that.
Yes, definitely. Here's a plan you can follow:
|
@rashadg1030 I like your application and I hope you will be able to use selective functors. However, I'm unsure how you plan to handle Matthew's example: foo :: Okapi Response
foo = do
pathSeg "foo"
x <- getArgs
pathSeg x
.... Trying to introspect this computation using selective functors will lead you to the infinite set of possible effect sequences, where the path segment |
@snowleopard Glad you brought this up since this was one concern I had as well. |
@rashadg1030 My question is partly about performance but also about your intent. I can believe that you can write a function that type checks and gives you this (possibly infinite) list of possible effects. But what would you do with such a list? |
@snowleopard Two things about the example: foo :: Okapi Response
foo = do
pathSeg "foo"
x <- getArgs
pathSeg x
....
Using the above example, I basically want to be able to know what
The goal is to generate routes, so I need to know the |
@rashadg1030 It's hard for me to give any definitive answers. It seems best to just give it a try and see what happens. I suggest not to jump into implementing support for |
During commenting on #57 I found the following brain teaser. Let us assume we have a statement like: thong :: Monad m => m (t -> Either a b) -> m t -> m (a -> b) -> m b
thong x y z = do
f <- x
r <- y
case f r of
Left a -> ($ a) <$> z
Right b -> pure b How do we desugar it? Maybe like this: thing :: Selective f => f (a1 -> Either a2 b) -> f a1 -> f (a2 -> b) -> f b
thing x y z = (x <*> y) <*? z But I don't know a reason why the
It has a different type than before, but maybe it is possible to define a desugaring of the do notation where |
@turion Aha, cool! I see where you are getting at. To simplify a bit, consider the following snippet: execAndSelect :: Monad m => m () -> m (Either a b) -> m (a -> b) -> m b
execAndSelect x y z = do
x
ab <- y
case ab of
Left a -> ($ a) <$> z
Right b -> return b This can be desugared into |
Maybe one feels more natural, but reasoning about do notation can be very confusing if rigidity isn't given. We need to be able to reason about each part of the do notation locally. Imagine you have more than three statements. Maybe it isn't obvious immediately in which parts we will need And when you want to analyse the code, it is important to be able to disect the statement locally. This isn't given here anymore: We cannot look at the first two statements in isolation anymore. We might believe that this means anything: do
x
ab <- y And we might reason something like "Ah, That's why I believe that rigidity is necessary to make |
@turion I feel like you are arguing my case that With |
No, I don't think so: My point is that we might reason about |
Has any more work been done on this? Personally, I find the |
Not that I know of. I don't think there is enough interest/motivation for someone to actually do the work.
I don't understand what you mean. |
It seems to me that
Selective
functors could in principle desugar more do notations thanApplicative
s. For example:I believe this has the same semantics as:
The latter only needs
Selective
.In general, all
if
statements could be desugared intoSelective
, andcase
could be desugared iff the variables bound in the branches are not used in a binding way.I've asked some of this here: https://discourse.haskell.org/t/efficient-selectivedo-and-binary-search/1657/3 but I wanted to know your opinion. I think this would be interesting, especially in situations where no efficient monads exist, e.g. some streaming approaches.
The text was updated successfully, but these errors were encountered: