spooky: Unified API for phantom typed newtypes and type aliases

[ bsd3, data, library ] [ Propose Tags ]

In scenarios where newtypes are not well supported, we may need to use type aliases instead for various reasons. This means we give away type safety and static analysis tooling. We can get this safety back with Spooky. When we compile with -f typed, we get a type check using newtypes. When compiled without the flag we get the same code type checking with type aliases.


[Skip to Readme]

Modules

[Index] [Quick Jump]

Flags

Manual Flags

NameDescriptionDefault
untyped

Should Spooky be an alias?

Disabled
typed

Should Spooky be newtyped?

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Note: This package has metadata revisions in the cabal description newer than included in the tarball. To unpack the package including the revisions, use 'cabal get'.

Maintainer's Corner

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.1.0.0
Dependencies base (>=4.9 && <5) [details]
License BSD-3-Clause
Author Isaac Shapira
Maintainer isaac.shapira@platonic.systems
Revised Revision 1 made by fresheyeball at 2021-12-13T06:56:52Z
Category data
Source repo head: git clone https://gitlab.com/fresheyeball/spooky.git -b master
Uploaded by fresheyeball at 2021-12-13T06:36:13Z
Distributions NixOS:0.1.0.0
Downloads 45 total (1 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for spooky-0.1.0.0

[back to package description]

👻

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"