Skip to content

Commit

Permalink
Beta 1 (#36)
Browse files Browse the repository at this point in the history
Major improvements to structure, docs, and tests

- Added Semigroupoid -> Category -> Arrow
- Added Foldable -> Traversable
- Added Extend -> Comonad
- Added Bifunctor
- Fixed do-notation
- Split `chain do` and `monad _ do`
  • Loading branch information
expede authored Aug 1, 2017
1 parent ed38c1a commit 3e0566b
Show file tree
Hide file tree
Showing 43 changed files with 3,880 additions and 843 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: elixir
elixir:
- 1.3.2
- 1.5.0
otp_release:
- 19.0.2
- 20.0
script: mix test; mix credo --strict
after_script:
- MIX_ENV=docs mix do deps.get, inch.report
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![](./brand/Wordmark/PNG/[email protected])
![](https://github.com/expede/witchcraft/raw/master/brand/Wordmark/PNG/[email protected])

`Witchcraft` is a library providing common algebraic and categorical abstractions to Elixir.
(Monoids, functors, monads, arrows, and categories)
Expand All @@ -7,7 +7,8 @@

A big thank you to [Brandon Labbé](https://dribbble.com/brandonlabbe) for creating the logo to this project

# Table of Contents
# README
## Table of Contents
- [Quick Start](#quick-start)
- [Values](#values)
- [Beginner Friendliness](#beginner-friendliness)
Expand All @@ -30,6 +31,59 @@ def deps do
end
```

# Relationship to Other Packages
```
Quark TypeClass
↘ ↙
Witchcraft
Algae
```

* [Quark](https://hex.pm/packages/quark): Standard combinators (`id`, `compose`, &c)
* [TypeClass](https://hex.pm/packages/type_class): Used internally to generate type classes
* [Algae](https://hex.pm/packages/algae): Algebraic data types that implement Witchcraft type classes

# Hierarchy

```
Semigroupoid Semigroup Setoid Foldable Functor -----------┐
↓ ↓ ↓ ↓ ↙ ↓ ↘ |
Category Monoid Ord Traversable Apply Bifunctor |
↓ ↙ ↘ ↓
Arrow Applicative Chain Extend
↘ ↙ ↓
Monad Comonad
```

It is very common to want everything in a chain. You can import the entire chain
with `use`. For example:

```elixir
use Witchcraft.Monad
```

Any options that you pass to `use` will be propagated all the way down the chain

```elixir
use Witchcraft.Monad, except: [~>: 2]
```

Some modules override `Kernel` operators and functions. While this is generally safe,
if you would like to skip all overrides, pass `override_kernel: false` as an option

```elixir
use Witchcraft.Foldable, override_kernel: false
```

Finally, to import the entire library:

```elixir
use Witchcraft
```

# Values
## Beginner Friendliness
As much as possible, keep things friendly. Concrete examples are available in the
Expand Down
Binary file added brand/Icon/PNG/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/all.json

Large diffs are not rendered by default.

48 changes: 40 additions & 8 deletions lib/witchcraft.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
defmodule Witchcraft do
@moduledoc "Convenient top-level `use`"
@moduledoc """
Top level module
defmacro __using__(_) do
quote do
use Witchcraft.Monoid
## Hierarchy
Semigroupoid Semigroup Setoid Foldable Functor -----------┐
↓ ↓ ↓ ↓ ↙ ↓ ↘ |
Category Monoid Ord Traversable Apply Bifunctor |
↓ ↙ ↘ ↓
Arrow Applicative Chain Extend
↘ ↙ ↓
Monad Comonad
## `use Wicthcraft`
There is a convenient `use` macro to import *all* functions in the library.
use Witchcraft
This recursively calls `use` on all children modules.
use Witchcraft.Functor
use Witchcraft.Traversable
use Witchcraft.Applicative
use Witchcraft.Monad
Any options passed to `use` will be passed down to all dependencies.
use Witchcraft, execpt: [right_fold: 2]
If you would like to not override the functions and operators from `Kernel`,
you can pass the special option `override_kernel: false`.
use Witchcraft, override_kernel: false
This same style of `use` is also available on all submodules, and follow
the dependency chart (above).
"""

defmacro __using__(opts \\ []) do
quote do
use Witchcraft.Arrow, unquote(opts)
use Witchcraft.Monoid, unquote(opts)
use Witchcraft.Bifunctor, unquote(opts)
use Witchcraft.Traversable, unquote(opts)
use Witchcraft.Monad, unquote(opts)
use Witchcraft.Comonad, unquote(opts)
end
end
end
151 changes: 140 additions & 11 deletions lib/witchcraft/applicative.ex
Original file line number Diff line number Diff line change
@@ -1,27 +1,137 @@
import TypeClass

defclass Witchcraft.Applicative do
@moduledoc """
`Applicative` extends `Apply` with the ability to lift value into a
particular data type or "context".
This fills in the connection between regular function application and `Apply`
data --------------- function ---------------> result
| | |
of(Container, data) of(Container, function) of(Container, result)
↓ ↓ ↓
%Container<data> --- %Container<function> ---> %Container<result>
## Type Class
An instance of `Witchcraft.Applicative` must also implement `Witchcraft.Apply`,
and define `Witchcraft.Applicative.of/2`.
Functor [map/2]
Apply [ap/2]
Applicative [of/2]
"""

alias __MODULE__
extend Witchcraft.Apply

defmacro __using__(_) do
@type t :: any()

defmacro __using__(opts \\ []) do
quote do
import Witchcraft.Apply
import unquote(__MODULE__)
use Witchcraft.Apply, unquote(opts)
import unquote(__MODULE__), unquote(opts)
end
end

where do
@doc """
Bring a value into the same data type as some sample
## Examples
iex> of([], 42)
[42]
iex> of([1, 2, 3], 42)
[42]
iex> of({"a", "b", 155}, 42)
{"", "", 42}
iex> of(fn -> nil end, 42).(55)
42
iex> of(fn(a, b, c) -> a + b - c end, 42).(55)
42
iex> import Witchcraft.Apply
...>
...> []
...> |> of(&+/2)
...> |> curried_ap([1, 2, 3])
...> |> ap(of([], 42))
[43, 44, 45]
"""
@spec of(Applicative.t(), any()) :: Applicative.t()
def of(sample, to_wrap)
end

@doc """
Partially apply `of/2`, generally as a way to bring many values into the same context
## Examples
iex> to_tuple = of({"very example", "much wow"})
...> [to_tuple.(42), to_tuple.("hello"), to_tuple.([1, 2, 3])]
[{"", 42}, {"", "hello"}, {"", [1, 2, 3]}]
"""
@spec of(Applicative.t()) :: (any() -> Applicative.t())
def of(sample), do: fn to_wrap -> of(sample, to_wrap) end

@doc """
Alias for `of/2`, for cases that this helps legibility or style
## Example
iex> wrap({":|", "^.~"}, 42)
{"", 42}
iex> [] |> wrap(42)
[42]
"""
@spec wrap(Applicative.t(), any()) :: Applicative.t()
defalias wrap(sample, to_wrap), as: :of

@doc """
Alias for `of/2`, for cases that this helps legibility or style
## Example
iex> pure({"ohai", "thar"}, 42)
{"", 42}
iex> [] |> pure(42)
[42]
"""
@spec pure(Applicative.t(), any()) :: Applicative.t()
defalias pure(sample, to_wrap), as: :of

@doc """
Alias for `of/2`, for cases that this helps legibility or style
## Example
iex> unit({":)", ":("}, 42)
{"", 42}
iex> [] |> unit(42)
[42]
"""
@spec unit(Applicative.t(), any()) :: Applicative.t()
defalias unit(sample, to_wrap), as: :of

properties do
use Witchcraft.Apply
import Witchcraft.Functor
import Witchcraft.Apply

def identity(data) do
a = generate(data)
Expand All @@ -31,31 +141,50 @@ defclass Witchcraft.Applicative do
end

def homomorphism(data) do
arg = 42
a = generate(data)
f = &inspect/1

left = Applicative.of(data, f) <<~ Applicative.of(data, a)
right = Applicative.of(data, f.(a))
left = Applicative.of(a, arg) ~>> Applicative.of(a, f)
right = Applicative.of(a, f.(arg))

equal?(left, right)
end

def interchange(data) do
arg = 42
as = generate(data)
fs = replace(as, &inspect/1)

left = fs <<~ Applicative.of(fs, as)
right = Applicative.of(fs, fn g -> g.(as) end) <<~ fs
left = Applicative.of(as, arg) ~>> fs
right = fs ~>> Applicative.of(as, fn g -> g.(arg) end)

equal?(left, right)
end
end
end

definst Witchcraft.Applicative, for: Function do
def of(_, unwrapped), do: &Quark.SKI.k(unwrapped, &1)
end

definst Witchcraft.Applicative, for: List do
def of(_, unwrapped), do: [unwrapped]
end

# definst Witchcraft.Applicative, for: Functio💯n do
# def wrap(fun) when is_function(fun), do: &Quark.SKI.k/1
# end
definst Witchcraft.Applicative, for: Tuple do
custom_generator(_) do
import TypeClass.Property.Generator, only: [generate: 1]
{generate(0), generate(0)}
end

def of(sample, unwrapped) do
size = tuple_size(sample)

sample
|> elem(0)
|> Witchcraft.Monoid.empty()
|> Tuple.duplicate(size)
|> put_elem(size - 1, unwrapped)
end
end
Loading

0 comments on commit 3e0566b

Please sign in to comment.