@hackage spooky0.1.0.0

Unified API for phantom typed newtypes and type aliases

  • Installation

  • Dependencies (1)

  • Dependents (0)

  • Package Flags

      untyped
       (off by default)

      Should Spooky be an alias?

      typed
       (on by default)

      Should Spooky be newtyped?

👻

Concept

This is a package designed to help optimize around scenarios with bad newtype support without losing type safety.

Spooky :: (s :: Type) -> (a :: Type) -> Type

If type a can be represented as type s, we can make s spooky. While we usually might use terms of type a, we need to represent a as a term of type s instead because of some tragedy. Converting our types to s directly loses important type information (knowledge of a is erased). We could preserve this information by placing a in a phantom typed newtype over s. But then we incuring cost around this newtype, that is not incurred by a type alias. However, if we use a type alias over s to get a into a phantom type, we get no additional safety over using s directly. Only readability is gained with the type alias.

This library resolves this tension

Data.Spooky.Spooky is a newtype or a type alias depending on cabal flags. This means you can develop with Spooky as a newtype and get all the benefits of the static type checking and static analysis in tooling, while for production use a type alias.

This technique was originally motivated by the need to reduce file sizes when using the Haskell to Plutus compiler in 2021.

The Typed Version

This is the default. You may pass a cabal flag -f typed for symmetry, but it wont do anything.

newtype Spooky (s :: Type) (a :: Type) = Spooky s

Note

Keep in mind with the typed version the following works. So we have some real safety:

 λ. newtype Typed a = Typed String deriving Show
 λ. typedId = id :: Typed Int -> Typed Int
 λ. y = Typed "wat" :: Typed String
 λ. typedId y

<interactive>:21:9: error:
    • Couldn't match type ‘[Char]’ with ‘Int’
      Expected type: Typed Int
        Actual type: Typed String
    • In the first argument of ‘typedId’, namely ‘y’
      In the expression: typedId y
      In an equation for ‘it’: it = typedId y

Untyped Version

This is not the default. Supplied along side the newtyped version, and can be enabled with a cabal flag.

cabal build -f untyped

will replace the newtype with an alias

type Spooky (s :: Type) (a :: Type) = s

This allows for some safety as we can hold type information in the phantom type representing the semantics of our code.

Note

Keep in mind the following works. So not that much safety is provided here:

 λ. type Untyped a = String
 λ. untypedId = id :: Untyped Int -> Untyped Int
 λ. x = "wat" :: Untyped String
 λ. untypedId x
"wat"