It’s interesting how the tuple notation seems to perform a boolean union of the type constraints (resulting type needs to match all constraints) vs union types denoted with |, performing different type of union (resulting type needs to match any). I wonder what the equivalent infix operator would be then.

If you wanted to have alternates you have to manually encode it. Something like this:

{-# LANGUAGE RankNTypes, ConstraintKinds, KindSignatures #-}
import GHC.Exts (Constraint)
data Has (c :: * -> Constraint) = forall x. (c x) => Has x
showAnyNumber :: Either (Has RealFrac) (Has Integral) -> String
showAnyNumber (Left (Has real)) = show (toRational real)
showAnyNumber (Right (Has int)) = show (toInteger int)
main = do
let numbers = [ Left (Has 3.14), Right (Has 73) ]
mapM_ (putStrLn . showAnyNumber) numbers

In showAnyNumber, when you match on the Has constructor it brings the constraint “into scope” so you can use the methods on it (toRational with RealFrac/toInteger with Integral).

However, existential types like this aren’t that useful in practice—here you’d be better calling show when you know the concrete type.

It’s interesting how the tuple notation seems to perform a boolean union of the type constraints (resulting type needs to match all constraints) vs union types denoted with

`|`

, performing different type of union (resulting type needs to match any). I wonder what the equivalent infix operator would be then.If you wanted to have alternates you have to manually encode it. Something like this:

In

`showAnyNumber`

, when you match on the`Has`

constructor it brings the constraint “into scope” so you can use the methods on it (`toRational`

with`RealFrac`

/`toInteger`

with`Integral`

).However, existential types like this aren’t that useful in practice—here you’d be better calling

`show`

when you know the concrete type.