Skip to content

Commit

Permalink
Major reorganization (#27)
Browse files Browse the repository at this point in the history
Still a bunch more to do, but this is already much better. Merging to master, and will continue to tighten stuff up and work through the roadmap.
  • Loading branch information
expede authored Sep 12, 2016
1 parent a7d88f7 commit ec595f7
Show file tree
Hide file tree
Showing 33 changed files with 597 additions and 900 deletions.
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: elixir
elixir:
- 1.3.2
otp_release:
- 19.0.2
script: mix test; mix credo --strict
after_script:
- MIX_ENV=docs mix do deps.get, inch.report
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
![](./logo.png)
![](./brand/logo.png)

`Witchcraft` is a library providing common algebraic and categorical abstractions to Elixir.
(Monoids, functors, monads, arrows, and categories)

| Build Status | Maintainer Message | Documentation | Hosted Package |
|--------------|--------------------|---------------|----------------|
| [![Circle CI](https://circleci.com/gh/robot-overlord/witchcraft/tree/master.svg?style=svg)](https://circleci.com/gh/robot-overlord/witchcraft/tree/master) | ![built with humanity](https://cloud.githubusercontent.com/assets/1052016/11023213/66d837a4-8627-11e5-9e3b-b295fafb1450.png) |[robotoverlord.io/witchcraft](http://www.robotoverlord.io/witchcraft/extra-readme.html) | [Hex](https://hex.pm/packages/witchcraft) |
[![Build Status](https://travis-ci.org/expede/witchcraft.svg?branch=master)](https://travis-ci.org/expede/witchcraft) [![Inline docs](http://inch-ci.org/github/expede/witchcraft.svg?branch=master)](http://inch-ci.org/github/expede/witchcraft) [![Deps Status](https://beta.hexfaktor.org/badge/all/github/expede/witchcraft.svg)](https://beta.hexfaktor.org/github/expede/witchcraft) [![hex.pm version](https://img.shields.io/hexpm/v/witchcraft.svg?style=flat)](https://hex.pm/packages/witchcraft) [![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](http://hexdocs.pm/witchcraft/) [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/expede/witchcraft/blob/master/LICENSE)

# Table of Contents
- [Quick Start](#quick-start)
Expand Down
File renamed without changes
12 changes: 0 additions & 12 deletions circle.yml

This file was deleted.

16 changes: 16 additions & 0 deletions config/.credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
%{
configs: [
%{
name: "default",
files: %{
included: ["lib/", "test"],
excluded: []
},
checks: [
{Credo.Check.Consistency.TabsOrSpaces},
{Credo.Check.Design.AliasUsage, false},
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 100}
]
}
]
}
53 changes: 9 additions & 44 deletions lib/witchcraft.ex
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
defmodule Witchcraft do
@moduledoc ~S"""
For convenience, the most common functions and operators are exported at the top-level
"""
@moduledoc "Convenient top-level `use`"

# ======
# Monoid
# ======
defdelegate identity(specemin), to: Witchcraft.Monoid
defmacro __using__(_) do
quote do
use Witchcraft.Monoid

defdelegate append(a, b), to: Witchcraft.Monoid
defdelegate a <|> b, to: Witchcraft.Monoid.Operator

# =======
# Functor
# =======
defdelegate lift(data, function), to: Witchcraft.Functor
defdelegate lift(function), to: Witchcraft.Functor.Function
defdelegate lift(), to: Witchcraft.Functor.Function

defdelegate data ~> func, to: Witchcraft.Functor.Operator
defdelegate func <~ data, to: Witchcraft.Functor.Operator

defdelegate replace(functor_data, const), to: Witchcraft.Functor.Function

# ===========
# Applicative
# ===========
defdelegate wrap(specemin, bare), to: Wicthcraft.Applicative

defdelegate apply(wrapped_value, wrapped_function), to: Witchcraft.Applicative
defdelegate value ~>> fun, to: Witchcraft.Applicative.Operator
defdelegate fun <<~ value, to: Witchcraft.Applicative.Operator

# Figure out the liftAs

defdelegate seq_first([a,b]), to: Witchcraft.Applicative.Function
defdelegate seq_second([a,b]), to: Witchcraft.Applicative.Function

# =====
# Monad
# =====
defdelegate join(deep), to: Witchcraft.Monad

defdelegate bind(wrapped, fun), to: Witchcraft.Monad.Function
defdelegate wrapped >>> fun, to: Witchcraft.Monad.Operator
defdelegate fun <<< wrapped, to: Witchcraft.Monad.Operator
use Witchcraft.Functor
use Witchcraft.Applicative
use Witchcraft.Monad
end
end
end
File renamed without changes.
197 changes: 25 additions & 172 deletions lib/witchcraft/applicative.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defprotocol Witchcraft.Applicative do
defmodule Witchcraft.Applicative do
@moduledoc """
Applicative functors provide a method of seqing a function contained in a
data structure to a value of the same type. This allows you to seq and compose
Expand All @@ -9,209 +9,62 @@ defprotocol Witchcraft.Applicative do
## Identity
`seq`ing a lifted `id` to some lifted value `v` does not change `v`
`seq(v, wrap(&id(&1))) == v`
seq(v, wrap(&id(&1))) == v
## Composition
`seq` composes normally.
`seq((wrap &compose(&1,&2)), (seq(u,(seq(v, w))))) == seq(u,(seq(v, w)))`
seq((wrap &compose(&1,&2)), (seq(u,(seq(v, w))))) == seq(u,(seq(v, w)))
## Homomorphism
`seq`ing a `wrap`ped function to a `wrap`ped value is the same as wrapping the
result of the function on that value.
`seq(wrap x, wrap f) == wrap f(x))`
seq(wrap x, wrap f) == wrap f(x))
## Interchange
The order does not matter when `seq`ing to a `wrap`ped value
and a `wrap`ped function.
`seq(wrap y, u) == seq(u, wrap &(lift(y, &1))`
seq(wrap y, u) == seq(u, wrap &(lift(y, &1))
## Functor
Being an applicative _functor_, `seq` behaves as `lift` on `wrap`ped values
`lift(x, f) == seq(x, (wrap f))`
lift(x, f) == seq(x, (wrap f))
# Notes
Given that Elixir functons are right-associative, you can write clean looking,
but much more ambiguous versions:
`wrap(y) |> seq(u) == seq(u, wrap(&lift(y, &1)))`
`lift(x, f) == seq(x, wrap f)`
wrap(y) |> seq(u) == seq(u, wrap(&lift(y, &1)))
lift(x, f) == seq(x, wrap f)
However, it is strongly recommended to include the parentheses for clarity.
"""

@fallback_to_any true

@doc ~S"""
Lift a pure value into a type provided by some specemin (usually the zeroth
or empty value of that type, but not nessesarily).
"""
@spec wrap(any, any) :: any
def wrap(specimen, bare)

@doc ~S"""
Sequentially seq lifted function(s) to lifted data.
"""
@spec seq(any, (... -> any)) :: any
def seq(wrapped_value, wrapped_function)
end

defimpl Witchcraft.Applicative, for: Any do
@doc ~S"""
By default, use the true identity functor (ie: don't wrap)
"""
def wrap(_, bare_value), do: bare_value

@doc ~S"""
For un`wrap`ped values, treat `seq` as plain function application.
"""
def seq(bare_value, bare_function), do: Quark.Curry.curry(bare_function).(bare_value)
end

defimpl Witchcraft.Applicative, for: List do
import Quark.Curry, only: [curry: 1]

@doc ~S"""
```elixir
iex> wrap([], 0)
[0]
```
"""
def wrap(_, bare), do: [bare]

@doc ~S"""
```elixir
iex> seq([1,2,3], [&(&1 + 1), &(&1 * 10)])
[2,3,4,10,20,30]
iex> import Witchcraft.Functor, only: [lift: 2]
iex> seq([9,10,11], lift([1,2,3], &(fn x -> x * &1 end)))
[9,10,11,18,20,22,27,30,33]
```
"""
def seq(_, []), do: []
def seq(values, [fun|funs]) do
Enum.map(values, curry(fun)) ++ Witchcraft.Applicative.seq(values, funs)
defmacro __using__(_) do
quote do
import unquote(__MODULE__)
require Witchcraft.Applicative.Function
use Witchcraft.Functor
end
end
end

# Algae.Id
# ========

defimpl Witchcraft.Applicative, for: Algae.Id do
import Quark.Curry, only: [curry: 1]
alias Algae.Id, as: Id

@doc ~S"""

```elixir
iex> %Algae.Id{} |> wrap(9)
%Algae.Id{id: 9}
```
"""
def wrap(_, bare), do: %Algae.Id{id: bare}
defdelegate wrap(bare), to: Witchcraft.Applicative.Wrap

@doc ~S"""
```elixir
defdelegate wrap(specemin, bare), to: Witchcraft.Applicative.Protocol
defdelegate seq(applicative, fun), to: Witchcraft.Applicative.Protocol

iex> seq(%Algae.Id{id: 42}, %Algae.Id{id: &(&1 + 1)})
%Algae.Id{id: 43}
defdelegate rewrap(specemin), to: Witchcraft.Applicative.Function
defdelegate lift(val_1, val_2, fun), to: Witchcraft.Applicative.Function
defdelegate lift(val_1, val_2, val_3, fun), to: Witchcraft.Applicative.Function
defdelegate lift(val_1, val_2, val_3, val_4, fun), to: Witchcraft.Applicative.Function

iex> import Witchcraft.Functor, only: [lift: 2]
iex> alias Algae.Id, as: Id
iex> seq(%Id{id: 9}, lift(%Id{id: 2}, &(fn x -> x + &1 end)))
%Algae.Id{id: 11}
defdelegate seq_first(first, second), to: Witchcraft.Applicative.Function
defdelegate seq_second(first, second), to: Witchcraft.Applicative.Function

```
"""
def seq(%Id{id: value}, %Id{id: fun}), do: %Id{id: curry(fun).(value)}
end

# Algae.Maybe
# ===========

defimpl Witchcraft.Applicative, for: Algae.Maybe.Nothing do
def wrap(%Algae.Maybe.Nothing{}, bare), do: %Algae.Maybe.Just{just: bare}

def seq(%Algae.Maybe.Nothing{}, %Algae.Maybe.Nothing{}), do: %Algae.Maybe.Nothing{}
def seq(%Algae.Maybe.Nothing{}, %Algae.Maybe.Just{just: _}), do: %Algae.Maybe.Nothing{}
end

defimpl Witchcraft.Applicative, for: Algae.Maybe.Just do
import Quark.Curry, only: [curry: 1]

def wrap(%Algae.Maybe.Just{just: _}, bare), do: %Algae.Maybe.Just{just: bare}

def seq(%Algae.Maybe.Just{just: _}, %Algae.Maybe.Nothing{}), do: %Algae.Maybe.Nothing{}
def seq(%Algae.Maybe.Just{just: value}, %Algae.Maybe.Just{just: fun}) do
%Algae.Maybe.Just{just: curry(fun).(value)}
end
end

# Algae.Either
# ============

defimpl Witchcraft.Applicative, for: Algae.Either.Left do
def wrap(%Algae.Either.Left{left: _}, bare), do: %Algae.Either.Right{right: bare}

def seq(%Algae.Either.Left{left: value}, %Algae.Either.Right{right: _}) do
%Algae.Either.Left{left: value}
end
end

defimpl Witchcraft.Applicative, for: Algae.Either.Right do
import Quark.Curry, only: [curry: 1]
import Witchcraft.Functor.Operator, only: [~>: 2]

def wrap(%Algae.Either.Right{right: _}, bare), do: %Algae.Either.Right{right: bare}

def seq(%Algae.Either.Right{right: value}, %Algae.Either.Right{right: fun}) do
%Algae.Either.Right{right: value ~> fun}
end
end

# Algae.Free
# ==========
# Based on Ørjan Johansen's Free Applicative

defimpl Witchcraft.Applicative, for: Algae.Free.Shallow do
import Witchcraft.Functor.Operator, only: [~>: 2]

def wrap(%Algae.Free.Shallow{shallow: _}, bare), do: Algae.Free.shallow(bare)
def seq(%Algae.Free.Shallow{shallow: shallow}, other), do: other ~> &(&1.(shallow))
end

defimpl Witchcraft.Applicative, for: Algae.Free.Deep do
import Quark, only: [compose: 2]
import Witchcraft.Functor.Operator, only: [<~: 2]
import Witchcraft.Applicative.Operator, only: [<<~: 2]

def wrap(%Algae.Free.Deep{deep: _, deeper: _}, bare), do: Algae.Free.shallow(bare)

def seq(%Algae.Free.Deep{deep: deep, deeper: deeper}, %Algae.Free.Shallow{shallow: shallow}) do
new_deeper = &compose(&1, &2) <~ %Algae.Free.Shallow{shallow: shallow} <<~ deeper
Algae.Free.deep(new_deeper, deep)
end

def seq(%Algae.Free.Deep{deep: deep1, deeper: deeper1}, %Algae.Free.Deep{deep: deep2, deeper: deeper2}) do
new_deeper = &compose(&1, &2) <~ %Algae.Free.Deep{deep: deep2, deeper: deeper2} <<~ deeper1
Algae.Free.deep(new_deeper, deep1)
end
defdelegate data ~>> func, to: Witchcraft.Applicative.Operator
defdelegate func <<~ data, to: Witchcraft.Applicative.Operator
end
Loading

0 comments on commit ec595f7

Please sign in to comment.