简体   繁体   中英

Haskell can't figure out types from GADT constructor

Apologies in advance for the long example, I couldn't figure out a shorter one.

Let's define a type class Box , that does nothing but contain another type, the Content .

{-# LANGUAGE GADTs                     #-}
{-# LANGUAGE TypeFamilies              #-}

class Box t where
    type Content t

data IntBox = IntBox
data StringBox = StringBox

Let's write a few instances:

instance Box IntBox where
    type Content IntBox = Int

instance Box StringBox where
    type Content StringBox = String

data JointBox a b = JointBox a b

instance (Box a, Box b) => Box (JointBox a b) where
    type Content (JointBox a b) = Either (Content a) (Content b)

That all compiles and works so far. Enter GADTs. I want an algebraic data type that is constructed out of a box and its contents. The constructor decides the box type completely.

data ABox t where
    AnIntBox :: IntBox -> ABox IntBox
    AStringBox :: StringBox -> ABox StringBox
    AnIntOrStringBox :: JointBox IntBox StringBox -> ABox (JointBox IntBox StringBox)

Now, in my mind, this would mean that by pattern matching on the constructor of ABox , the type of the box and its contents should be determined. But this doesn't seem to be the case:

frobABox :: (Content t) ->  ABox t              -> IO ()
frobABox     int           (AnIntBox _)         =  print $ int + 3
frobABox     string        (AStringBox _)       =  putStrLn $ reverse string
frobABox    (Left int)     (AnIntOrStringBox _) =  print $ int + 6
frobABox    (Right string) (AnIntOrStringBox _) =  putStrLn $ reverse string

This throws a lot of errors, amongst which these two seem the most important to me:

GADTAndTypeClassBug.hs:29:14:
    Couldn't match expected type ‘Content t’
                with actual type ‘Either t0 t1’
    The type variables ‘t0’, ‘t1’ are ambiguous
    Relevant bindings include
      frobABox :: Content t -> ABox t -> IO ()
        (bound at GADTAndTypeClassBug.hs:27:1)
    In the pattern: Left int
    In an equation for ‘frobABox’:
        frobABox (Left int) (AnIntOrStringBox _) = print $ int + 6

GADTAndTypeClassBug.hs:30:14:
    Couldn't match expected type ‘Content t’
                with actual type ‘Either t2 t3’
    The type variables ‘t2’, ‘t3’ are ambiguous
    Relevant bindings include
      frobABox :: Content t -> ABox t -> IO ()
        (bound at GADTAndTypeClassBug.hs:27:1)
    In the pattern: Right string
    In an equation for ‘frobABox’:
        frobABox (Right string) (AnIntOrStringBox _)
          = putStrLn $ reverse string

GADTAndTypeClassBug.hs:30:71:
    Couldn't match expected type ‘[Char]’ with actual type ‘t3’
      ‘t3’ is untouchable
        inside the constraints (t ~ JointBox IntBox StringBox)
        bound by a pattern with constructor
                   AnIntOrStringBox :: JointBox IntBox StringBox
                                       -> ABox (JointBox IntBox StringBox),
                 in an equation for ‘frobABox’
        at GADTAndTypeClassBug.hs:30:29-46
    Relevant bindings include
      string :: t3 (bound at GADTAndTypeClassBug.hs:30:20)
    In the first argument of ‘reverse’, namely ‘string’
    In the second argument of ‘($)’, namely ‘reverse string’

An easier example without the type families works:

data UnitOrEither t where
    AUnit :: () -> UnitOrEither ()
    AnEither :: Either String Int -> UnitOrEither (Either String Int)

frob :: UnitOrEither t -> IO ()
frob   (AUnit _)       =  print ()
frob   (AnEither _)    =  print "Either"

So what's the problem?

Refinement for GADT pattern matches works left-to-right. So a type refinement resulting from matching on the second argument of frobABox will not apply to the first.

You can make the code compile by flipping the arguments of frobABox :

frobABox' :: ABox t              -> Content t -> IO ()
frobABox' (AnIntBox _)          int           =  print $ int + 3
frobABox' (AStringBox _)        string        =  putStrLn $ reverse string
frobABox' (AnIntOrStringBox _) (Left int)     =  print $ int + 6
frobABox' (AnIntOrStringBox _) (Right string) =  putStrLn $ reverse string

frobABox :: (Content t) ->  ABox t              -> IO ()
frobABox = flip frobABox'

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