MyNixOS website logo
Description

Automatically generate dual constructions.

A library for defining duals automatically, as well as labeling duals in existing packages.

→ dualizer ←

Delete half (minus ε) of your Haskell code!

Join the chat at https://gitter.im/dualizer/Lobby

Dualizer allows you to eliminate the dual of all your code. Rather than implementing, say, Comonad directly, you can define it in terms of its dual – Monad:

-- indicates that Functor is its own dual
labelSelfDual ''Functor

-- expands to:
--
--   class Functor f => Coapplicative f where
--     extract :: f a -> a -- the dual of pure
makeDualClass ''Applicative "Coapplicative" [('pure, "extract")]

-- expands to:
--
--   class Coapplicative m => Comonad m where
--     (=>>) :: m b -> (m b -> a) -> m a
makeDualClass ''Monad "Comonad" [('(>>=) , "=>>")]

See Categorical.Dual.Example for a bit more.

The Template Haskell You Need to Know

This library is written using Template Haskell, and while it tries to minimize the familiarity needed to use it (and accepting suggestions/PRs for reducing it further), some still leaks through. Here’s what you need to know.

When naming an existing type, prefix with '' (e.g., ''Either), and for an existing value, prefix with ' (e.g., 'fmap). Names of things to be created are plain Strings.

To allow code to be reified into an AST that Template Haskell can work with, it uses a special “quasiquotation” syntax, opening with [d| and closing with |] that looks like [d|your :: Code -> Here|] when used. There are variants of this that use something other than d in the opening, but we don’t need them in this library.

Defining Duals

There are three approaches here to defining duals, and they are listed in order of preference.

  1. define them simultaneously
  2. define the dual of an existing thing
  3. label two existing things as duals of each other

Simultaneous definition is the only way to automatically define duals of expressions. If you define the dual of an existing value, you will only get its type, and you will still need to provide the expression.

You can, however, still label existing values as duals of each other.

Defining Duals Simultaneously

makeDualDec
  [d| cata :: Functor f => (f a -> a) -> Fix f -> a
      cata f = f . fmap (cata f) . unfix |]
  "ana"

makeDualDec [d|type Algebra f a = f a -> a|] "Coalgebra"

This form can also be nested, allowing the definition of duals for type classes, etc. (NB: This can’t actually work this way).

makeDualDec [d|
  class Functor f => Applicative f where
    $$(makeDualDec [d|pure :: a -> f a|] "extract")
|] "Coapplicative"

makeDualDec [d|
  class Applicative f => Monad f where
    $$(makeDualDec [d|>>= :: f a -> (a -> f b) -> f b|] "=>>")
    fail :: f ()
|] "Comonad"

Defining the Dual of an Existing Thing

If one side of the construct already exists, then you can assign the duals like

makeDualType 'cata "ana"
makeDualType ''Algebra "Coalgebra"
makeDualClass ''Applicative "Coapplicative" [('pure, "extract")]
makeDualClass ''Monad "Comonad" [('(>>=) , "=>>")]

Labeling Existing Duals

Labeling is especially useful when things are duals of themselves.

labelSelfDual ''Functor
labelSelfDual 'fmap -- not implied by the former because:

class Steppable t f | t -> f where
  project :: t -> f t
  embed :: f t -> t

labelSelfDual ''Steppable
labelDual 'project 'embed

Also, if there are things that are both equivalent to some other thing, you can label one as “semi-dual”, mapping in one direction but not the other.

labelDual 'pure 'extract

-- `return` is overconstrained, so we let it dualize to `extract`, but `extract`
-- will be converted to `pure` on any return trip.
labelSemiDual 'return 'extract
Metadata

Version

0.1.0.1

License

Unknown

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows