简体   繁体   中英

Haskell: Couldn't match type when using ImplicitParams with Rank2Types

I'm trying to use ImplicitParams together with Rank2Types in order to build a custom Monad for my application. However, I get the error below, which I don't understand.

Here is a complete example:

{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE ImplicitParams             #-}
{-# LANGUAGE Rank2Types                 #-}
{-# LANGUAGE MultiParamTypeClasses      #-}

import Control.Monad.Reader
import Control.Monad.Random hiding (random)
import Control.Monad.Random.Class

data Hint = Hint { good :: Int
                 , bad  :: Int }
  deriving (Eq, Show)

class Combination c where
  random   :: Env c
  fromList :: [Int] -> Env c
  toList   :: c -> Env [Int]  

class Combination c => Set s c where
  empty     :: (?combination :: c) => s
  fromList' :: [c] -> s
  member    :: c -> s -> Bool
  insert    :: c -> s -> s

  notMember :: c -> s -> Bool
  notMember c s = not (member c s)

type Env' a = Rand StdGen a

newtype Env a = Env { unenv :: forall i1 i2 c s .
                      Integral i1
                      => Integral i2
                      => Combination c
                      => Set s c
                      => (?colors :: Int)
                      => (?holes  :: Int)
                      => (?hint   :: c -> c -> Env Hint)
                      => (?combination :: c)
                      => (?set    :: s)
                      => (?cardinality :: i1)
                      => (?powers :: [i2])
                      => Env' a }

instance Functor Env where
  fmap f (Env a) = Env $ fmap f a

instance Applicative Env where
  pure = Env . pure
  (Env f) <*> (Env a) = Env $ f <*> a

instance Monad Env where
  (Env m) >>= f = Env $ m >>= \a -> let (Env b) = f a in b

instance MonadRandom Env where
  getRandomR  = Env . getRandomR
  getRandom   = Env getRandom
  getRandomRs = Env . getRandomRs
  getRandoms  = Env getRandoms



-- Dummy instances
instance Combination Int where
  random = undefined
  fromList _ = undefined
  toList _ = undefined

instance Set Float Int where
  empty = undefined
  fromList' = undefined
  member = undefined
  insert = undefined


-- Tests
f :: forall c i.
     Combination c
  => Integral i
  => (?colors :: Int)
  => (?cardinality :: i)
  => (?combination :: c)
  => (?hint :: c -> c -> Env Hint)
  => c -> Env String
f _ = do
  r <- getRandomR (100::Int, 200)
  secret <- random :: Env c
  r1 <- random :: Env c
  h <- (?hint secret r1)
  return $
    show ?colors
    ++ " " ++ (show $ fromIntegral ?cardinality)
    ++ " " ++ show r

g :: forall c.
     Combination c
  => (?combination :: c)
  => Env String
g = do
  r <- random :: Env c
  Env $ unenv $ f r
  --            ^^^
  --            ERROR: Couldn't match type ‘c1’ with ‘c’...

I tried almost everything to make it work without any success, here is the error message :

Internal.hs:92:17: error:
    • Couldn't match type ‘c1’ with ‘c’
        arising from a functional dependency between constraints:
          ‘?combination::c’ arising from a use of ‘f’ at Internal.hs:92:17-19
          ‘?combination::c1’
            arising from a type expected by the context:
                           forall i1 i2 c s.
                           (Integral i1, Integral i2, Combination c, Set s c, ?colors::Int,
                            ?holes::Int, ?hint::c -> c -> Env Hint, ?combination::c, ?set::s,
                            ?cardinality::i1, ?powers::[i2]) =>
                           Env' a0
            at Internal.hs:92:3-19
      ‘c1’ is a rigid type variable bound by
        a type expected by the context:
          forall i1 i2 c1 s.
          (Integral i1, Integral i2, Combination c1, Set s c1, ?colors::Int,
           ?holes::Int, ?hint::c1 -> c1 -> Env Hint, ?combination::c1,
           ?set::s, ?cardinality::i1, ?powers::[i2]) =>
          Env' String
        at Internal.hs:92:3-19
      ‘c’ is a rigid type variable bound by
        the type signature for:
          g :: forall c. (Combination c, ?combination::c) => Env String
        at Internal.hs:(86,1)-(89,15)
    • In the second argument of ‘($)’, namely ‘f r’
      In the second argument of ‘($)’, namely ‘unenv $ f r’
      In a stmt of a 'do' block: Env $ unenv $ f r
    • Relevant bindings include r :: c (bound at Internal.hs:91:3)
   |
92 |   Env $ unenv $ f r
   |                 ^^^

The point is that I don't want to add every implicit parameter to the signature of every single function in my program in case I want to add other parameters in the future.

Thanks.

EDIT: I just discovered PartialTypeSignatures which could solve my problem in a much simpler way, I'm trying this out now!

Instead of

Env $ unenv $ f r

you should simply use

f r

Doing Env $ unenv $ ... introduces a new bunch of rigid type variables i1 i2 cs , which have to be matched by unenv $ ... and GHC meets some ambiguity here, I guess. There should be some way to force unenv $ ... to use the exact same rigid variables, but it's not worth it, since we can avoid that.

Further, to call fr you also have to define the other implicit arguments like ?cardinality, ?colors, ?hint . Otherwise, you'll still get an error.

Ok so I came up with a solution using PartialTypeSignatures . I could have avoided signatures altogether, but in some cases I need to bring in some constraints. The only downside is that I still need to define full signatures on type class methods, but I can live with it. Here is an example :

{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE ImplicitParams        #-}

class SomeClass a where
  method :: Real t => (?var1 :: t) => IO a

instance SomeClass Int where
  method = return $ floor $ realToFrac $ ?var1 + 1

instance SomeClass Float where
  method = return $ realToFrac $ ?var1 + 1

someFun :: _ => SomeClass a => a -> IO a
someFun x = do
  v <- method
  return $ v + ?var2

f :: forall t. _ => SomeClass t => (?proxy :: t) => IO t
f = (method :: IO t) >>= ?fun

g :: _ => Num t => IO t
g = do
  v <- f
  return $ v + 1

v1 :: IO Int
v1 =
  let
    ?var1 = 10
    ?var2 = 20
    ?proxy = undefined :: Int
  in let
    ?fun = someFun
  in g

v2 :: IO Float
v2 =
  let
    ?var1 = 10.1
    ?var2 = 20.2
    ?proxy = undefined :: Float
  in let
    ?fun = someFun
  in g

main :: IO ()
main = do
  r1 <- v1
  r2 <- v2
  print r1
  print r2

I'm still open to suggestions though :) .

Side note: One downside of implicit parameters is that you must bring them into scope as soon as you want to reference a function or a value with implicit parameters constraints. As a consequence you cannot compose those functions without bringing all their parameters into scope. A potential solution to this problem is explained in this blog post .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM