I want to have a data type which contains booleans and doubles strictly in an alternating fashion. Like this:
tw0 = TWInit True
tw1 = TWInit True :-- 0.5
tw2 = TWInit True :-- 0.5 :- False
tw3 = TWInit True :-- 0.5 :- False :-- 0.5
With the following code, all of the above can be typed:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PatternSynonyms #-}
data TWEnd = TWESample | TWETime
data TimedWord (end :: TWEnd) where
TWInit :: Bool -> TimedWord 'TWESample
TWTime :: TimedWord 'TWESample -> Double -> TimedWord 'TWETime
TWSample :: TimedWord 'TWETime -> Bool -> TimedWord 'TWESample
pattern (:--) :: TimedWord 'TWESample -> Double -> TimedWord 'TWETime
pattern t :-- x = TWTime t x
pattern (:-) :: TimedWord 'TWETime -> Bool -> TimedWord 'TWESample
pattern t :- s = TWSample t s
However, it is not clear to me how to type something like this:
printTimedWord (TWInit sample) = "TWInit " ++ show sample
printTimedWord (tw :-- x) = printTimedWord tw ++ " :-- " ++ show x
printTimedWord (tw :- sample) = printTimedWord tw ++ " :- " ++ show sample
Haskell does not seem to like the above definition because the first argument is both of type TimedWord TWETime and TimedWord TWESample. How can I fix this?
If you have a
TimedWord end(for some unknown type variableend), then pattern matching on the GADT constructors just works (an explicit type signature is necessary though):So your problem is not "how to accept GADTs with different type indexes", but rather that it seems pattern synonyms don't play nicely with this GADT feature. I'm not sure if it's impossible to write pattern synonyms that work like GADT constructors, or whether there's just something extra you have to do.
However it seems (at least from your simple example) that you simply would rather use infix operators than named constructors? So an alternative way to fix your problem is to ditch the pattern synonyms and simply use
:--and:-as the constructors in the first place:I know this is unlikely to be your entire problem, but if you use the operators as the real constructors and provide infix declarations for them, then GHC can simply derive a
Showinstance for you that basically matches what you're trying to do withprintTimedWord(but requiresStandaloneDeriving, if you're not usingGHC2021):With this we can see:
So you don't necessarily need to implement
printTimedWordat all.(I have no idea what associativity or precedence is actually appropriate, I just picked something arbitrarily; with no infix declarations the derived
Showinstance prints the constructors in prefix form like(:--) (TWInit True) 3.0. Infix declarations are probably a good idea regardless though, whether they're pattern synonyms or real constructors)