2

Is there any binder in haskell to introduce a type variable (and constraints) quantified in a type ?

I can add an extra argument, but it defeats the purpose.

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE GADTs #-}


data Exists x = forall m. Monad m => Exists (m x)

convBad ::  x -> Exists x  
convBad  x = Exists @m (return @m x, undefined) --Not in scope: type variable ‘m’typecheck


data Proxy (m:: * -> *) where Proxy :: Proxy m

convOk ::  Monad m => x -> Proxy m -> Exists x 
convOk  x (_ :: Proxy m) = Exists (return @m x)
nicolas
  • 9,549
  • 3
  • 39
  • 83

1 Answers1

4

To bring type variables into scope, use forall (enabled by ExplicitForall, which is implied by ScopedTypeVariables):

convWorksNow :: forall m x. Monad m => x -> Exists x  
convWorksNow x = Exists (return @m x)

-- Usage:
ex :: Exists Int
ex = convWorksNow @Maybe 42

But whether you do it like this or via Proxy, keep in mind that m must be chosen at the point of creating Exists. So whoever calls the Exists constructor must know what m is.

If you wanted it to be the other way around - i.e. whoever unwraps an Exists value chooses m, - then your forall should be on the inside:

newtype Exists x = Exists (forall m. Monad m => m x)

convInside :: x -> Exists x
convInside x = Exists (return x)

-- Usage:
ex :: Exists Int
ex = convInside 42

main = do
  case ex of
    Exists mx -> mx >>= print  -- Here I choose m ~ IO

  case ex of
    Exists mx -> print (fromMaybe 0 mx)  -- Here I choose m ~ Maybe

Also, as @dfeuer points out in the comments, note that your original type definition (the one with forall on the outside) is pretty much useless beyond just signifying the type of x (same as Proxy does). This is because whoever consumes such value must be able to work with any monad m, and you can do anything with a monad unless you know what it is. You can't bind it inside IO, because it's not necessarily IO, you can't pattern match it with Just or Nothing because it's not necessarily Maybe, and so on. The only thing you can do with it is bind it with >>=, but then you'll just get another instance of it, and you're back to square one.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • Excellent answer, but the OP shouldn't need to add `ExplicitForAll` because [ScopedTypeVariables](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/scoped_type_variables.html) already implies it. – Robin Zigmond Mar 25 '21 at 16:56
  • Thank you @RobinZigmond, I suspected as much, but couldn't find docs quickly. – Fyodor Soikin Mar 25 '21 at 16:58
  • `Exists` seems like an odd name for that `Forall` thing. – dfeuer Mar 25 '21 at 19:30
  • 1
    You might want to point out that the existential version is quite useless; `Exists x` is basically the same as `Proxy x`. The only time you might want an existential sort of like this is if you're using it to keep a value alive (for GC purposes), in which case you won't want the `Monad` constraint. – dfeuer Mar 25 '21 at 19:38
  • 1
    @dfeuer added an explanation – Fyodor Soikin Mar 25 '21 at 21:24
  • of course ! what was I thinking... ! I had an application in mind expressing a universal property (quantified over monads). I should revisit the application and repost. – nicolas Mar 26 '21 at 08:48