open-union

Extensible, type-safe unions.

https://github.com/bfops/open-union

Latest on Hackage:0.4.0.0

This package is not currently in any snapshots. If you're interested in using it, we recommend adding it to Stackage Nightly. Doing so will make builds more reliable, and allow stackage.org to host generated Haddocks.

MIT licensed by Zeke Foppa
Maintained by [email protected]

Extensible, type-safe unions. This package is very new and likely to change.

Basic usage example (language tags ommitted due to https://github.com/haskell/cabal/issues/774)

import Data.OpenUnion
type MyUnion = Union '[Char, Int, [()]]
showMyUnion :: MyUnion -> String
showMyUnion
    =  (\(c :: Char) -> "char: " ++ show c)
    @> (\(i :: Int) -> "int: " ++ show i)
    @> (\(l :: [()]) -> "list length: " ++ show (length l))
    @> (\(s :: String) -> "string: " ++ s)
    @> typesExhausted
main :: IO ()
main = do
    putStrLn $ showMyUnion $ liftUnion (4 :: Int)
    putStrLn $ showMyUnion $ liftUnion 'a'
    putStrLn $ showMyUnion $ liftUnion [(), ()]

which prints:

int: 4
char: 'a'
list length: 2

Casting to an unrelated type does not cause errors; In the above example,showMyUnion contains a String case despite MyUnion not containing String - superfluous cases are ignored, for the time being.

typesExhausted is NOT a catchall. It is a null case, and using it as a catchall (or forgetting to provide a certain case, for instance) will result in an error like:

example.hs:12:8:
    Couldn't match type ‘Int : ('[] :\ [Char])’ with ‘'[]’
    Expected type: Union ('[Int] :\ String) -> String
      Actual type: Union '[] -> String
    In the second argument of ‘(@>)’, namely ‘typesExhausted’
    In the second argument of ‘(@>)’, namely
      ‘(\ (s :: String) -> "string: " ++ s) @> typesExhausted’

The left-hand parts of the `: (think type-level (:)) are the cases that still need to be satisfied.

Trying to lift an incorrect type to a Union will cause an error resembling:

example.hs:20:30:
    No instance for (Data.OpenUnion.Internal.LiftToUnion '[] [Char])
      arising from a use of ‘liftUnion’
    In the second argument of ‘($)’, namely ‘liftUnion "asdf"’
    In the second argument of ‘($)’, namely
      ‘showMyUnion $ liftUnion "asdf"’
    In a stmt of a 'do' block:
      putStrLn $ showMyUnion $ liftUnion "asdf"

The original use case for this library was code like this (snipped from some record/playback logic):

type TrackStates = '[Stopped, Recording, Playing]

startRecording
  :: Union (TrackStates :\ Recording)
  -> ([Note], Union '[Recording])

The (:\\) type-level operator is for removal from a set, i.e. startRecording can be applied to a track in any state except the Recording state.