Skip to content

Commit

Permalink
Checked algebraic laws, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
sphaso committed Oct 15, 2023
1 parent 5570241 commit d5d9c36
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Set up Elixir
uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f
uses: erlef/setup-beam@v1
with:
elixir-version: '1.13'
otp-version: '24.1'
otp-version: '25'
- name: Restore dependencies cache
uses: actions/cache@v3
with:
Expand Down
6 changes: 3 additions & 3 deletions lib/noether/either.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Noether.Either do
These type of values will be then called `Either`.
"""
@type either :: {:ok, any()} | {:error, any()}
@type fun0 :: (() -> any())
@type fun0 :: (-> any())
@type fun1 :: (any() -> any())

@doc """
Expand Down Expand Up @@ -60,8 +60,7 @@ defmodule Noether.Either do
{:error, "Value not found"}
"""
@spec join(either()) :: either()
def join({:ok, {:ok, a}}), do: {:ok, a}
def join({:ok, {:error, a}}), do: {:error, a}
def join(a = {_, {_, _}}), do: bind(a, & &1)
def join(a = {:error, _}), do: a

@doc """
Expand All @@ -84,6 +83,7 @@ defmodule Noether.Either do
@doc """
Given an `{:ok, value}` and a function that returns an Either value, it applies the function on the `value`. It effectively "squashes" an `{:ok, {:ok, v}}` or `{:ok, {:error, _}}` to its most appropriate representation.
If an `{:error, _}` is given, it is returned as-is.
Please be careful and only use bind with functions that return either {:ok, _} or {:error, _}, otherwise you will break the Associativity law.
## Examples
Expand Down
20 changes: 9 additions & 11 deletions lib/noether/maybe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ defmodule Noether.Maybe do
## Examples
iex> map(nil, &Kernel.abs/1)
iex> Noether.Maybe.map(nil, &Kernel.abs/1)
nil
iex> map(-1, &Kernel.abs/1)
iex> Noether.Maybe.map(-1, &Kernel.abs/1)
1
"""
@spec map(any(), fun1()) :: any()
Expand Down Expand Up @@ -46,8 +46,12 @@ defmodule Noether.Maybe do
:hello
"""
@spec maybe(any(), fun1(), any()) :: any()
def maybe(nil, _, default), do: default
def maybe(a, f, _) when is_function(f, 1), do: f.(a)
def maybe(a, f, default) when is_function(f, 1) do
case map(a, f) do
nil -> default
b -> b
end
end

@doc """
Given a list of values, the function is mapped only on the elements different from `nil`. `nil` values will be discarded. A list of the results is returned.
Expand Down Expand Up @@ -85,12 +89,6 @@ defmodule Noether.Maybe do
"""
@spec choose(any(), fun1(), fun1()) :: any()
def choose(a, f, g) when is_function(f, 1) and is_function(g, 1) do
b = f.(a)

if is_nil(b) do
g.(a)
else
b
end
maybe(a, f, map(a, g))
end
end
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Noether.MixProject do
def project do
[
app: :noether,
version: "0.2.4",
version: "0.2.5",
elixir: "~> 1.13",
start_permanent: Mix.env() == :prod,
aliases: aliases(),
Expand Down Expand Up @@ -35,9 +35,9 @@ defmodule Noether.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:credo, "~> 1.6.4", only: :dev, runtime: false},
{:credo, "~> 1.7", only: :dev, runtime: false},
{:ex_doc, "~> 0.28.4", only: :dev},
{:dialyxir, "~> 1.1", only: :dev, runtime: false},
{:dialyxir, "~> 1.3", only: :dev, runtime: false},
{:stream_data, "~> 0.5", only: :test}
]
end
Expand Down
8 changes: 4 additions & 4 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"},
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"},
"dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"},
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
Expand Down
51 changes: 51 additions & 0 deletions test/noether/either_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,55 @@
defmodule Noether.EitherTest do
use ExUnit.Case
doctest Noether.Either, import: true
use ExUnitProperties

alias Noether.Either

describe "Functor laws" do
property "Identity" do
check all(n <- integer()) do
id = & &1
el = {:ok, n}
elx = {:error, n}
assert el == Either.map(el, id)
assert elx == Either.map(elx, id)
assert el == Either.map_error(el, id)
assert elx == Either.map_error(elx, id)
end
end

property "Composition" do
check all(n <- integer()) do
el = {:ok, n}
elx = {:error, n}

assert el |> Either.map(&(&1 + 1)) |> Either.map(&(&1 * 2)) ==
Either.map(el, &((&1 + 1) * 2))

assert elx |> Either.map_error(&(&1 + 1)) |> Either.map_error(&(&1 * 2)) ==
Either.map_error(elx, &((&1 + 1) * 2))
end
end
end

describe "Monad laws" do
property "Left identity" do
check all(n <- integer()) do
assert Either.wrap(Either.bind({:ok, n}, &{:ok, &1 + 1})) == {:ok, n + 1}
end
end

property "Right identity" do
check all(n <- integer()) do
assert Either.bind({:ok, n}, &Either.wrap/1) == {:ok, n}
end
end

property "Associativity" do
check all(n <- integer()) do
assert Either.bind(Either.bind({:ok, n}, &{:ok, &1 + 1}), &{:ok, &1 * 2}) ==
Either.bind({:ok, n}, &{:ok, (&1 + 1) * 2})
end
end
end
end
19 changes: 19 additions & 0 deletions test/noether/maybe_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
defmodule Noether.MaybeTest do
use ExUnit.Case
doctest Noether.Maybe, import: true
use ExUnitProperties

alias Noether.Maybe

describe "Functor laws" do
property "Identity" do
check all(n <- integer()) do
id = & &1
assert is_nil(Maybe.map(nil, id))
assert n == Maybe.map(n, id)
end
end

property "Composition" do
check all(n <- integer()) do
assert n |> Maybe.map(&(&1 + 1)) |> Maybe.map(&(&1 * 2)) == Maybe.map(n, &((&1 + 1) * 2))
end
end
end
end

0 comments on commit d5d9c36

Please sign in to comment.