TypeClass v1.0.0 TypeClass
Helpers for defining (bootstrapped, semi-)principled type classes
Generates a few modules and several functions and aliases. There is no need to use these internals directly, as the top-level API will suffice for actual productive use.
Example
defclass Semigroup do
use Operator
where do
@operator :<|>
def concat(a, b)
end
properties do
def associative(data) do
a = generate(data)
b = generate(data)
c = generate(data)
left = a |> Semigroup.concat(b) |> Semigroup.concat(c)
right = Semigroup.concat(a, Semigroup.concat(b, c))
left == right
end
end
end
definst Semigroup, for: List do
def concat(a, b), do: a ++ b
end
defclass Monoid do
extend Semigroup
where do
def empty(sample)
end
properties do
def left_identity(data) do
a = generate(data)
Semigroup.concat(empty(a), a) == a
end
def right_identity(data) do
a = generate(data)
Semigroup.concat(a, empty(a)) == a
end
end
end
definst Monoid, for: List do
def empty(_), do: []
end
Internal Structure
A type_class
is composed of several parts:
- Dependencies
- Protocol
- Properties
Dependencies
Dependencies are the other type classes that the type class being defined extends. For instance, Monoid has a Semigroup dependency.
It only needs the immediate parents in the chain, as those type classes will have performed all of the checks required for their parents.
Protocol
defclass Foo
generates a Foo.Proto
submodule that holds all of the functions
to be implemented (it’s a normal protocol). It’s a very lightweight & straightforward,
but The Protocol
should never need to be called explicitly.
Macro: where do
Optional
Properties
Being a (quasi-)principled type class also means having properties. Users must define at least one property, plus at least one sample data generator. These will be run at compile time and refuse to compile if they don’t pass.
All custom structs need to implement the TypeClass.Property.Generator
protocol.
This is called automatically by the prop checker. Base types have been implemented
by this library.
Please note that class functions are aliased to the last segment of their name.
ex. Foo.Bar.MyClass.quux
is automatically usable as MyClass.quux
in the proprties
block
Macro: properties do
Non-optional
Summary
Macros
Variant of conforms/2
that can be called within a data module
Check that a datatype conforms to the class hierarchy and properties
Delegate to a local function
Top-level wrapper for all type class modules. Used as a replacement for defmodule
Variant of definst/2
for use inside of a defstruct
module definition
Define an instance of the type class. The rough equivalent of defimpl
.
defimpl
will check the properties at compile time, and prevent compilation
if the datatype does not conform to the protocol
Define properties that any instance of the type class must satisfy. They must by unary (takes a data seed), and return a boolean (true if passes)
Describe functions to be instantiated. Creates an internal protocol
Macros
Variant of conforms/2
that can be called within a data module
Top-level wrapper for all type class modules. Used as a replacement for defmodule
.
Examples
defclass Semigroup do
where do
def concat(a, b)
end
properties do
def associative(data) do
a = generate(data)
b = generate(data)
c = generate(data)
left = a |> Semigroup.concat(b) |> Semigroup.concat(c)
right = Semigroup.concat(a, Semigroup.concat(b, c))
left == right
end
end
end
Variant of definst/2
for use inside of a defstruct
module definition
Define an instance of the type class. The rough equivalent of defimpl
.
defimpl
will check the properties at compile time, and prevent compilation
if the datatype does not conform to the protocol.
Examples
definst Semigroup, for: List do
def concat(a, b), do: a ++ b
end
Define properties that any instance of the type class must satisfy. They must by unary (takes a data seed), and return a boolean (true if passes).
generate
is automatically imported
Examples
defclass Semigroup do
# ...
properties do
def associative(data) do
a = generate(data)
b = generate(data)
c = generate(data)
left = a |> Semigroup.concat(b) |> Semigroup.concat(c)
right = Semigroup.concat(a, Semigroup.concat(b, c))
left == right
end
end
end