Check samples to confirm that your data adheres to monoidal properties
+
Check samples of your monoid to confirm that your data adheres to the
+monoidal properties. All members of your datatype should adhere to these rules.
+There are placed here as a quick way to spotcheck some of your values.
iex> alias Witchcraft.Monoid, as: Monoid
+iex> defimpl Monoid, for: Integer do
+iex> def identity(_), do: 0
iex> def op(a, b), do: a + b
iex> end
iex> Monoid.op(1, 4) |> Monoid.op 2 |> Monoid.op 10
@@ -115,7 +116,7 @@
Counter-Example
Integer division is not a monoid.
Because you cannot divide by zero, the property does not hold for all values in the set.
Notes
-
You can of course use abuse this protocol to define a fake “monoid” that behaves differently.
+
You can of course use abuse this protocol to define a fake ‘monoid’ that behaves differently.
For the protocol to operate as intended, you need to respect the above properties.
Check samples to confirm that your data adheres to monoidal properties
+
Check samples of your monoid to confirm that your data adheres to the
+monoidal properties. All members of your datatype should adhere to these rules.
+There are placed here as a quick way to spotcheck some of your values
diff --git a/doc/index.html b/doc/index.html
index e4b71f8..96a2f3c 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -2,7 +2,7 @@
- Witchcraft v0.0.1 – Documentation
+ Witchcraft v0.1.0 – Documentation
diff --git a/lib/witchcraft.ex b/lib/witchcraft.ex
index 189db1e..d1539f5 100644
--- a/lib/witchcraft.ex
+++ b/lib/witchcraft.ex
@@ -1,2 +1,25 @@
defmodule Witchcraft do
+ import Witchcraft.Monoid
+ import Witchcraft.Monoid.Functions
+ import Witchcraft.Monoid.Properties
+
+ # import Witchcraft.Functor
+ # import Witchcraft.Functor.Functions
+ # import Witchcraft.Functor.Properties
+
+ # import Witchcraft.Applicative
+ # import Witchcraft.Applicative.Functions
+ # import Witchcraft.Applicative.Properties
+
+ # import Witchcraft.Monad
+ # import Witchcraft.Monad.Functions
+ # import Witchcraft.Monad.Properties
+
+ # import Witchcraft.Catgegory
+ # import Witchcraft.Catgegory.Functions
+ # import Witchcraft.Catgegory.Properties
+
+ # import Witchcraft.Arrow
+ # import Witchcraft.Arrow.Functions
+ # import Witchcraft.Arrow.Properties
end
diff --git a/lib/witchcraft/monoid.ex b/lib/witchcraft/monoid.ex
index 5756f94..5c279ad 100644
--- a/lib/witchcraft/monoid.ex
+++ b/lib/witchcraft/monoid.ex
@@ -1,6 +1,6 @@
defprotocol Witchcraft.Monoid do
@moduledoc ~S"""
- Monoids are a set, plus a binary combining operation (`op`) that
+ Monoids are a set of elements, and a binary combining operation (`op`) that
returns another member of the set.
# Properties
@@ -18,26 +18,29 @@ defprotocol Witchcraft.Monoid do
```
# Pseudocode
- id = 0
+ identity = 0
op = &(&1 + &2) # Integer addition
- op(34, id) == 34
+ op(34, identity) == 34
```
```
# Pseudocode
- id = 1
+ identity = 1
op = &(&1 * &2) # Integer multiplication
- op(42, id) == 42
+ op(42, identity) == 42
```
## Concrete
```
+
+ iex> alias Witchcraft.Monoid, as: Monoid
iex> defimpl Monoid, for: Integer do
- iex> def id(_), do: 0
+ iex> def identity(_), do: 0
iex> def op(a, b), do: a + b
iex> end
iex> Monoid.op(1, 4) |> Monoid.op 2 |> Monoid.op 10
17
+
```
## Counter-Example
@@ -45,19 +48,12 @@ defprotocol Witchcraft.Monoid do
Because you cannot divide by zero, the property does not hold for all values in the set.
# Notes
- You can of course use abuse this protocol to define a fake 'monoid' that behaves differently.
+ You can of course abuse this protocol to define a fake 'monoid' that behaves differently.
For the protocol to operate as intended, you need to respect the above properties.
"""
- @doc """
- Check if the argument is a member of the monoid.
- Doubles as a definition of what belongs to the monoid.
- """
- @spec member?(any) :: boolean
- def member?(value)
-
- @doc "Get the idenity ('zero') element of the monoid by passing in any element of the set"
- @spec id(any) :: any
+ @doc "Get the identity ('zero') element of the monoid by passing in any element of the set"
+ @spec identity(any) :: any
def identity(a)
@doc "Combine two members of the monoid, and return another member"
diff --git a/lib/witchcraft/monoid/functions.ex b/lib/witchcraft/monoid/functions.ex
index fff8df5..c3827cf 100644
--- a/lib/witchcraft/monoid/functions.ex
+++ b/lib/witchcraft/monoid/functions.ex
@@ -1,5 +1,24 @@
defmodule Witchcraft.Monoid.Functions do
alias Witchcraft.Monoid, as: Mon
+ @doc ~S"""
+ Infix variant of `Monoid.op`
+
+ # Example
+ ```
+
+ iex> alias Witchcraft.Monoid, as: Monoid
+ iex> defimpl Monoid, for: Integer do
+ iex> def identity(_), do: 0
+ iex> def op(a, b), do: a + b
+ iex> end
+ iex> Monoid.op(1, 4) |> Monoid.op 2 |> Monoid.op 10
+ 17
+ iex> 1 <|> 4 <|> 2 <|> 10
+ 17
+
+ ```
+ """
+ @spec any <|> any :: any
def a <|> b, do: Mon.op(a, b)
end
diff --git a/lib/witchcraft/monoid/properties.ex b/lib/witchcraft/monoid/properties.ex
index 129f6c4..1ff234a 100644
--- a/lib/witchcraft/monoid/properties.ex
+++ b/lib/witchcraft/monoid/properties.ex
@@ -1,27 +1,35 @@
defmodule Witchcraft.Monoid.Properties do
@moduledoc """
- Check samples to confirm that your data adheres to monoidal properties
+ Check samples of your monoid to confirm that your data adheres to the
+ monoidal properties. *All members* of your datatype should adhere to these rules.
+ They are placed here as a quick way to spotcheck some of your values.
"""
- alias Witchcraft.Monoid, as: Mon
+ import Witchcraft.Monoid
+ import Witchcraft.Monoid.Functions
- @type a :: any
-
- @spec confirm_membership([a]) :: boolean
- def confirm_membership(candidates) do
- Enum.reduce(candidates, true, &(&2 and Mon.member?(&1)))
+ @doc """
+ Check that some member of your monoid combines with the identity to return itself
+ """
+ @spec spotcheck_identity(any) :: boolean
+ def spotcheck_identity(member) do
+ (identity(member) <|> member) == member
end
- # @spec test_id :: boolean
- # def spotcheck_id(member) do
- # op(identity, member) == member
- # end
-
- # def spotcheck_associativity(member1, member2, member3) do
- # op(member1, member2) |> op(member3) == member1 |> op(op(member2, member3))
- # end
+ @doc ~S"""
+ Check that `Monoid.op` is [associative](https://en.wikipedia.org/wiki/Associative_property)
+ (ie: brackets don't matter)
+ """
+ @spec spotcheck_associativity(any, any, any) :: boolean
+ def spotcheck_associativity(member1, member2, member3) do
+ (member1 <|> (member2 <|> member3)) == ((member1 <|> member2) <|> member3)
+ end
- # def spotcheck(a, b, c) do
- # confirm_membership([a, b, c]) and spotcheck_id(a) and spotcheck_associativity(a, b, c)
- # end
+ @doc """
+ Spotcheck all monoid properties
+ """
+ @spec spotcheck(any, any, any) :: boolean
+ def spotcheck(a, b, c) do
+ spotcheck_identity(a) and spotcheck_associativity(a, b, c)
+ end
end
diff --git a/mix.exs b/mix.exs
index 10f347c..5e12c98 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,7 +5,7 @@ defmodule Witchcraft.Mixfile do
[app: :witchcraft,
name: "Witchcraft",
- version: "0.0.1",
+ version: "0.1.0",
elixir: "~> 1.1",
source_url: "https://github.com/robot-overlord/witchcraft",
diff --git a/test/monoid_test.exs b/test/monoid_test.exs
new file mode 100644
index 0000000..2c9ca55
--- /dev/null
+++ b/test/monoid_test.exs
@@ -0,0 +1,56 @@
+defmodule MonoidTest do
+ use ExUnit.Case
+
+ import Witchcraft.Monoid
+ import Witchcraft.Monoid.Functions
+ import Witchcraft.Monoid.Properties
+
+ doctest Witchcraft.Monoid
+ doctest Witchcraft.Monoid.Functions
+ doctest Witchcraft.Monoid.Properties
+
+ # Happy case: Strings
+ defimpl Witchcraft.Monoid, for: BitString do
+ def identity(_), do: ""
+ def op(a, b), do: a <> b
+ end
+
+ # Sad case: Malformed floats under division
+ defimpl Witchcraft.Monoid, for: Float do
+ def identity(_), do: -9.0
+ def op(a, b), do: a / b
+ end
+
+
+ test "identity always returns the same value for that datatype" do
+ assert identity("a") == identity("b")
+ end
+
+ test "identity combined with itself is the identity" do
+ assert identity("") <|> identity("") == identity("")
+ end
+
+ test "left identity" do
+ assert identity("welp") <|> "o hai" == "o hai"
+ end
+
+ test "right identity" do
+ assert "o hai" <|> identity("welp") == "o hai"
+ end
+
+ test "spotcheck_identity is true for well formed monoids" do
+ assert spotcheck_identity("well formed") == true
+ end
+
+ test "spotcheck_identity is false for malformed monoids" do
+ assert spotcheck_identity(88.8) == false
+ end
+
+ test "spotcheck_associativity returns true when monoid is well formed" do
+ assert spotcheck_associativity("a", "b", "c") == true
+ end
+
+ test "spotcheck_associativity returns false when monoid is poorly formed" do
+ assert spotcheck_associativity(-9.1, 42.0, 88.8) == false
+ end
+end