├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config └── config.exs ├── lib ├── control.ex ├── control │ ├── applicative.ex │ ├── applicative │ │ ├── either.ex │ │ ├── list.ex │ │ ├── maybe.ex │ │ └── writer.ex │ ├── functor.ex │ ├── functor │ │ ├── either.ex │ │ ├── list.ex │ │ ├── maybe.ex │ │ └── writer.ex │ ├── helpers.ex │ ├── monad.ex │ ├── monad │ │ ├── either.ex │ │ ├── list.ex │ │ ├── maybe.ex │ │ └── writer.ex │ ├── monoid.ex │ └── monoid │ │ └── list.ex └── data │ ├── either.ex │ ├── maybe.ex │ └── writer.ex ├── mix.exs ├── mix.lock └── test ├── control ├── applicative │ ├── either_test.exs │ ├── list_test.exs │ ├── maybe_test.exs │ └── writer_test.exs ├── functor │ ├── either_test.exs │ ├── list_test.exs │ ├── maybe_test.exs │ └── writer_test.exs ├── helpers_test.exs ├── monad │ ├── either_test.exs │ ├── list_test.exs │ ├── maybe_test.exs │ └── writer_test.exs └── monoid │ └── list_test.exs ├── control_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | doc/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 3 | - 1.3 4 | - 1.4 5 | - 1.5 6 | script: 7 | - mix test 8 | after_success: 9 | - MIX_ENV=test mix coveralls.travis 10 | - MIX_ENV=test mix dogma 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Shane Logsdon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Control 2 | [![Build Status](https://img.shields.io/travis/slogsdon/elixir-control.svg?style=flat)](https://travis-ci.org/slogsdon/elixir-control) 3 | [![Coverage Status](https://img.shields.io/coveralls/slogsdon/elixir-control.svg?style=flat)](https://coveralls.io/r/slogsdon/elixir-control) 4 | [![Hex.pm Version](http://img.shields.io/hexpm/v/control.svg?style=flat)](https://hex.pm/packages/control) 5 | 6 | An exploratory look into functors, applicatives, and monads for Elixir. See [the accompanying blog post](http://www.slogsdon.com/functors-applicatives-and-monads-in-elixir/) for more details. 7 | 8 | > More code, documentation, and tests are in progress. 9 | 10 | ## Installation 11 | 12 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed as: 13 | 14 | 1. Add control to your list of dependencies in `mix.exs`: 15 | 16 | def deps do 17 | [{:control, "~> 0.0.1"}] 18 | end 19 | 20 | 2. Ensure control is started before your application: 21 | 22 | def application do 23 | [applications: [:control]] 24 | end 25 | 26 | ## License 27 | 28 | Control is released under the MIT License. 29 | 30 | See [LICENSE](https://github.com/slogsdon/elixir-control/blob/master/LICENSE) for details. 31 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | config :dogma, 3 | exclude: [ 4 | ~r(test/), 5 | ] 6 | -------------------------------------------------------------------------------- /lib/control.ex: -------------------------------------------------------------------------------- 1 | defmodule Control do 2 | @moduledoc false 3 | end 4 | -------------------------------------------------------------------------------- /lib/control/applicative.ex: -------------------------------------------------------------------------------- 1 | defprotocol Control.Applicative do 2 | @moduledoc """ 3 | Applicatives (or more specifically applicative functors) 4 | are a special form of `Control.Functor` where the value 5 | within the functor is a function. 6 | """ 7 | 8 | @doc """ 9 | """ 10 | @spec apply(t, Control.Functor.t) :: t 11 | def apply(fun, f) 12 | end 13 | -------------------------------------------------------------------------------- /lib/control/applicative/either.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Applicative, for: Data.Either do 2 | def apply(%{left: l} = f, _) when l != nil, do: f 3 | def apply(%{right: fun}, f) do 4 | f |> Control.Functor.fmap(fun) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/control/applicative/list.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Applicative, for: List do 2 | def apply([] = f, _), do: f 3 | def apply(funs, f) do 4 | funs 5 | |> Enum.map(&(Control.Functor.fmap(f, &1))) 6 | |> List.flatten 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control/applicative/maybe.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Applicative, for: Data.Maybe do 2 | def apply(%{nothing: true} = f, _), do: f 3 | def apply(%{just: fun}, f) do 4 | f |> Control.Functor.fmap(fun) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/control/applicative/writer.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Applicative, for: Data.Writer do 2 | def apply(w, f) do 3 | f |> Control.Functor.fmap(w.value) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/control/functor.ex: -------------------------------------------------------------------------------- 1 | defprotocol Control.Functor do 2 | @moduledoc """ 3 | Functors are things that can be mapped over, like lists, 4 | `Maybe`s, trees, and such. 5 | 6 | Using functors, we can generalize how `Enum.map` works for 7 | `Enumerable`s on any data type, including `Maybe`. We can 8 | accomplish this by generalizing the action and implementing 9 | that action for the desired data types. This generalized 10 | action for functors is known as `fmap`. 11 | 12 | ## Laws 13 | 14 | All implementations of `Control.Functor` should obey the 15 | following implicit laws: 16 | 17 | f |> fmap(id) = id.(f) 18 | f |> fmap(q <|> p) = f |> fmap(p) |> fmap (q) 19 | 20 | where `f` is a functor, `id` is a function that returns 21 | its input, and `p` & `q` are functions. 22 | """ 23 | 24 | @doc """ 25 | The mapping function. 26 | """ 27 | @spec fmap(t, (term -> term)) :: t 28 | def fmap(functor, fun) 29 | end 30 | -------------------------------------------------------------------------------- /lib/control/functor/either.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Functor, for: Data.Either do 2 | def fmap(%{left: l} = f, _) when l != nil, do: f 3 | def fmap(%{right: r}, fun) do 4 | r 5 | |> fun.() 6 | |> Data.Either.right 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control/functor/list.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Functor, for: List do 2 | defdelegate fmap(list, fun), to: Enum, as: :map 3 | end 4 | -------------------------------------------------------------------------------- /lib/control/functor/maybe.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Functor, for: Data.Maybe do 2 | def fmap(%{nothing: true} = f, _), do: f 3 | def fmap(%{just: v}, fun) do 4 | v 5 | |> fun.() 6 | |> Data.Maybe.just 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control/functor/writer.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Functor, for: Data.Writer do 2 | def fmap(w, fun) do 3 | w.value 4 | |> fun.() 5 | |> Data.Writer.value 6 | |> Map.put(:log, w.log) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control/helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule Control.Helpers do 2 | @moduledoc """ 3 | A set of helper functions to ease the process of 4 | working with the `Control.*` protocols. 5 | """ 6 | 7 | alias Control.Monad 8 | 9 | @compile {:inline, ~>>: 2, <|>: 2} 10 | 11 | @doc """ 12 | Bind operator. 13 | 14 | The bind operator (`>>=` in Haskell) removes the need 15 | for piping to a call to `Control.Monad.bind/2`, so 16 | 17 | functor |> Control.Monad.bind(fun) 18 | 19 | becomes 20 | 21 | functor ~>> fun 22 | """ 23 | @spec (Monad.t ~>> (term -> Monad.t)) :: Monad.t 24 | def left ~>> right do 25 | Monad.bind(left, right) 26 | end 27 | 28 | @doc """ 29 | Compose operator. 30 | 31 | The compose operator (`.` in Haskell) removes the need 32 | for using `apply/2` or `.()` to call an anonymous 33 | function, so 34 | 35 | f.(g.(x)) or f |> apply([g |> apply([x])]) 36 | 37 | becomes 38 | 39 | ( 40 | """ 41 | @spec (Function.t <|> Function.t) :: Function.t 42 | def left <|> right do 43 | fn x -> left.(right.(x)) end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/control/monad.ex: -------------------------------------------------------------------------------- 1 | defprotocol Control.Monad do 2 | @moduledoc """ 3 | A monad is essentially a value with a context. 4 | 5 | Just as applicative functors are special versions of 6 | functors, monads are special versions of applicative 7 | functors. 8 | 9 | ## Laws 10 | 11 | All implementations of `Control.Monad` should obey the 12 | following implicit laws: 13 | 14 | type |> return(x) ~>> f = f(x) 15 | m ~>> return = m 16 | (m ~>> f) ~>> g = m ~>> &(f(&1) ~>> g) 17 | 18 | where `m` is a monad, `x` is a value, and `f` and `g` are 19 | both functions that return monadic values. 20 | """ 21 | 22 | @doc """ 23 | `return` takes a value and puts it in a minimal default 24 | context that still holds that value. In other words, it 25 | takes something and wraps it in a monad. 26 | """ 27 | @spec return(t) :: t 28 | @spec return(t, term) :: t 29 | def return(m, value \\ nil) 30 | 31 | @doc """ 32 | `bind` is similar to function application, only instead of 33 | taking a normal value and feeding it to a normal function, 34 | it takes a monadic value (that is, a value with a context) 35 | and feeds it to a function that takes a normal value but 36 | returns a monadic value. 37 | """ 38 | @spec bind(t, (term -> t)) :: t 39 | def bind(m, fun) 40 | end 41 | -------------------------------------------------------------------------------- /lib/control/monad/either.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Monad, for: Data.Either do 2 | def return(m, nil), do: m 3 | def return(%{right: nil, left: nil}, value) do 4 | Data.Either.right(value) 5 | end 6 | 7 | def bind(%{left: l} = m, _) when l != nil, do: m 8 | def bind(%{right: v}, fun) do 9 | v |> fun.() 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/control/monad/list.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Monad, for: List do 2 | def return(m, nil), do: m 3 | def return([], value) do 4 | [value] 5 | end 6 | 7 | def bind(xs, fun) do 8 | xs 9 | |> Enum.map(fun) 10 | |> List.flatten 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/control/monad/maybe.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Monad, for: Data.Maybe do 2 | def return(m, nil), do: m 3 | def return(%{just: nil, nothing: false}, value) do 4 | Data.Maybe.just(value) 5 | end 6 | 7 | def bind(%{nothing: true} = m, _), do: m 8 | def bind(%{just: v}, fun) do 9 | v |> fun.() 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/control/monad/writer.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Monad, for: Data.Writer do 2 | import Control.Monoid 3 | 4 | def return(w, nil), do: w 5 | def return(w, value) do 6 | w |> Map.put(:value, value) 7 | end 8 | 9 | def bind(%{value: v, log: t}, fun) do 10 | v 11 | |> fun.() 12 | |> Map.update!(:log, &(t |> mappend(&1))) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/control/monoid.ex: -------------------------------------------------------------------------------- 1 | defprotocol Control.Monoid do 2 | @moduledoc """ 3 | A monoid is a function and an identity for a given type. 4 | 5 | In more detail, a monoid is when you have an associative 6 | binary function and a value which acts as an identity 7 | with respect to that function. When something acts as an 8 | identity with respect to a function, it means that when 9 | called with that function and some other value, the 10 | result is always equal to that other value. `1` is the 11 | identity with respect to `*` and `[]` is the identity 12 | with respect to `++`. There are a lot of other monoids 13 | to be found in the world of Elixir, which is why the 14 | `Control.Monoid` protocol exists. It's for types which 15 | can act like monoids. 16 | 17 | ## Laws 18 | 19 | All implementations of `Control.Monoid` should obey the 20 | following implicit laws: 21 | 22 | mempty(type) |> mappend(x) = x 23 | x |> mappend(mempty(type)) = x 24 | mappend(x, y) |> mappend(z) = x |> mappend(y |> mappend(z)) 25 | 26 | where `type` is a type and `x`, `y`, and `z` are all values 27 | of that type. 28 | """ 29 | 30 | @doc """ 31 | The identity value. 32 | 33 | It should act as a polymorphic constant, but due to 34 | protocols needing a type for dispatching to the correct 35 | implementation, it requires a type to be passed as an 36 | argument. 37 | """ 38 | @spec mempty(t) :: t 39 | def mempty(a) 40 | 41 | @doc """ 42 | The binary function. 43 | 44 | It takes two values of the same type and returns a 45 | value of that type as well. 46 | """ 47 | @spec mappend(t, t) :: t 48 | def mappend(a, b) 49 | end 50 | -------------------------------------------------------------------------------- /lib/control/monoid/list.ex: -------------------------------------------------------------------------------- 1 | defimpl Control.Monoid, for: List do 2 | def mempty(_), do: [] 3 | defdelegate mappend(a, b), to: Kernel, as: :++ 4 | end 5 | -------------------------------------------------------------------------------- /lib/data/either.ex: -------------------------------------------------------------------------------- 1 | defmodule Data.Either do 2 | @moduledoc """ 3 | The `Data.Either` type represents values with 4 | two possibilities: `left(a)` or `right(b)`. 5 | 6 | The Either type is sometimes used to represent a 7 | value which is either correct or an error; by 8 | convention, the `left/1` constructor is used to 9 | hold an error value and the `right/1` constructor 10 | is used to hold a correct value (mnemonic: 11 | "right" also means "correct"). 12 | """ 13 | 14 | @type t :: %__MODULE__{ 15 | left: term, 16 | right: term 17 | } 18 | defstruct left: nil, 19 | right: nil 20 | 21 | @doc """ 22 | The left ("error") constructor. 23 | """ 24 | @spec left(term) :: t 25 | def left(v) do 26 | __MODULE__ |> struct(left: v) 27 | end 28 | 29 | @doc """ 30 | The right ("correct") constructor. 31 | """ 32 | @spec right(term) :: t 33 | def right(v) do 34 | __MODULE__ |> struct(right: v) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/data/maybe.ex: -------------------------------------------------------------------------------- 1 | defmodule Data.Maybe do 2 | @moduledoc """ 3 | The `Data.Maybe` type encapsulates an optional value. 4 | 5 | A value of type `Data.Maybe` either contains a 6 | value of type `a` (represented as `just(a)`), or it 7 | is empty (represented as `nothing`). Using 8 | `Data.Maybe` is a good way to deal with errors or 9 | exceptional cases without resorting to drastic 10 | measures such as error. 11 | 12 | The `Data.Maybe` type is also a monad. It is a 13 | simple kind of error monad, where all errors are 14 | represented by `nothing`. A richer error monad can be 15 | built using the `Data.Either` type. 16 | """ 17 | 18 | @type t :: %__MODULE__{ 19 | just: term, 20 | nothing: boolean 21 | } 22 | defstruct just: nil, 23 | nothing: false 24 | 25 | @doc """ 26 | Instantiates `Data.Maybe` value containing `v` of 27 | type `term`. 28 | """ 29 | @spec just(term) :: t 30 | def just(v) do 31 | __MODULE__ |> struct(just: v) 32 | end 33 | 34 | @doc """ 35 | Instantiates an "empty" `Data.Maybe` value. 36 | """ 37 | @spec nothing :: t 38 | def nothing do 39 | __MODULE__ |> struct(nothing: true) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/data/writer.ex: -------------------------------------------------------------------------------- 1 | defmodule Data.Writer do 2 | @moduledoc """ 3 | The `Data.Writer` type encapsulates a values with an 4 | attached, secondary value that acts like a log. 5 | 6 | `Data.Writer` allows for computations while making 7 | sure that all the log values are combined into one 8 | log value that then gets attached to the result. 9 | """ 10 | 11 | import Control.Monoid 12 | 13 | @type t :: %__MODULE__{ 14 | value: nil, 15 | log: [] 16 | } 17 | 18 | defstruct value: nil, 19 | log: [] 20 | 21 | @doc """ 22 | The log ("tell") helper. 23 | """ 24 | def log(%__MODULE__{} = writer, log) do 25 | writer 26 | |> Map.update!(:log, &(&1 |> mappend([log]))) 27 | end 28 | 29 | @doc """ 30 | The value constructor. 31 | """ 32 | def value(value) do 33 | __MODULE__ 34 | |> struct(value: value) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :control, 6 | version: "0.1.0", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | test_coverage: [tool: ExCoveralls], 11 | preferred_cli_env: [coveralls: :test, bench: :test], 12 | description: description(), 13 | package: package(), 14 | deps: deps()] 15 | end 16 | 17 | def application do 18 | [applications: [:logger]] 19 | end 20 | 21 | defp deps do 22 | [ 23 | {:earmark, "~> 1.2", only: :dev}, 24 | {:ex_doc, "~> 0.18", only: :dev}, 25 | 26 | {:excoveralls, "~> 0.7", only: :test}, 27 | {:dialyze, "~> 0.2", only: :test}, 28 | {:dogma, "~> 0.1", only: :test}, 29 | {:benchfella, "~> 0.3", only: :test}, 30 | ] 31 | end 32 | 33 | defp description do 34 | """ 35 | An exploratory look into functors, applicatives, and monads for Elixir. 36 | """ 37 | end 38 | 39 | defp package do 40 | [ 41 | maintainers: ["Shane Logsdon"], 42 | licenses: ["MIT"], 43 | links: %{"GitHub" => "https://github.com/slogsdon/elixir-control"}] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"benchfella": {:hex, :benchfella, "0.3.5", "b2122c234117b3f91ed7b43b6e915e19e1ab216971154acd0a80ce0e9b8c05f5", [:mix], []}, 2 | "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], []}, 3 | "dialyze": {:hex, :dialyze, "0.2.1", "9fb71767f96649020d769db7cbd7290059daff23707d6e851e206b1fdfa92f9d", [:mix], []}, 4 | "dogma": {:hex, :dogma, "0.1.15", "5bceba9054b2b97a4adcb2ab4948ca9245e5258b883946e82d32f785340fd411", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, optional: false]}]}, 5 | "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], []}, 6 | "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, 7 | "excoveralls": {:hex, :excoveralls, "0.7.5", "339e433e5d3bce09400dc8de7b9040741a409c93917849916c136a0f51fdc183", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, 8 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]}, 9 | "hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, optional: false]}, {:idna, "5.1.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]}, 10 | "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, optional: false]}]}, 11 | "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], []}, 12 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 13 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 14 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, 15 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, 16 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5", "2e73e068cd6393526f9fa6d399353d7c9477d6886ba005f323b592d389fb47be", [:make], []}, 17 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], []}} 18 | -------------------------------------------------------------------------------- /test/control/applicative/either_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Applicative.EitherTest do 2 | use ExUnit.Case 3 | alias Data.Either 4 | import Either 5 | alias Control.Applicative, as: A 6 | 7 | test "apply" do 8 | f = &(&1 + 1) 9 | assert right(f) 10 | == %Either{right: f, left: nil} 11 | assert right(f) |> A.apply(right(2)) 12 | == right(3) 13 | assert left("error") |> A.apply(right(2)) 14 | == left("error") 15 | assert left("error 1") |> A.apply(left("error 2")) 16 | == left("error 1") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/control/applicative/list_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Applicative.ListTest do 2 | use ExUnit.Case 3 | alias Control.Applicative, as: A 4 | 5 | test "apply" do 6 | f = &(&1 + 1) 7 | assert [f] |> A.apply([2]) 8 | == [3] 9 | assert [] |> A.apply([2]) 10 | == [] 11 | assert [] |> A.apply([]) 12 | == [] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/control/applicative/maybe_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Applicative.MaybeTest do 2 | use ExUnit.Case 3 | alias Data.Maybe 4 | import Maybe 5 | alias Control.Applicative, as: A 6 | 7 | test "apply" do 8 | f = &(&1 + 1) 9 | assert just(f) 10 | == %Maybe{just: f, nothing: false} 11 | assert just(f) |> A.apply(just(2)) 12 | == just(3) 13 | assert nothing() |> A.apply(just(2)) 14 | == nothing() 15 | assert nothing() |> A.apply(nothing()) 16 | == nothing() 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/control/applicative/writer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Applicative.WriterTest do 2 | use ExUnit.Case 3 | alias Data.Writer 4 | import Writer 5 | alias Control.Applicative, as: A 6 | 7 | test "apply" do 8 | f = &(&1 + 1) 9 | assert value(f) 10 | == %Writer{value: f, log: []} 11 | assert value(f) |> A.apply(value(2)) 12 | == value(3) 13 | assert value(f) |> log("msg 1") |> A.apply(value(2)) 14 | == value(3) 15 | assert value(f) |> A.apply(value(2) |> log("msg 1")) 16 | == value(3) |> log("msg 1") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/control/functor/either_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Functor.EitherTest do 2 | use ExUnit.Case 3 | alias Data.Either 4 | import Either 5 | import Control.Helpers 6 | import Control.Functor 7 | 8 | test "right" do 9 | assert right(1) 10 | == %Either{right: 1, left: nil} 11 | assert right(1) |> fmap(&(&1+1)) 12 | == %Either{right: 2, left: nil} 13 | end 14 | 15 | test "left" do 16 | assert left("error") 17 | == %Either{right: nil, left: "error"} 18 | assert left("error") |> fmap(fn _ -> left("next error") end) 19 | == %Either{right: nil, left: "error"} 20 | assert right(1) |> fmap(fn _ -> left("error") end) 21 | == %Either{right: %Either{right: nil, left: "error"}, left: nil} 22 | end 23 | 24 | test "laws" do 25 | f = right(1) 26 | id = fn x -> x end 27 | p = fn x -> x + 1 end 28 | q = fn x -> x + 2 end 29 | 30 | assert f |> fmap(id) == id.(f) 31 | assert f |> fmap(q <|> p) == f |> fmap(p) |> fmap(q) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/control/functor/list_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Functor.ListTest do 2 | use ExUnit.Case 3 | import Control.Helpers 4 | import Control.Functor 5 | 6 | test "fmap" do 7 | assert [] |> fmap(&(&1 + 1)) 8 | == [] 9 | assert [1,2,3] |> fmap(&(&1 * 2)) 10 | == [2,4,6] 11 | end 12 | 13 | test "laws" do 14 | f = [1] 15 | id = fn x -> x end 16 | p = fn x -> x + 1 end 17 | q = fn x -> x + 2 end 18 | 19 | assert f |> fmap(id) == id.(f) 20 | assert f |> fmap(q <|> p) == f |> fmap(p) |> fmap(q) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/control/functor/maybe_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Functor.MaybeTest do 2 | use ExUnit.Case 3 | alias Data.Maybe 4 | import Maybe 5 | import Control.Helpers 6 | import Control.Functor 7 | 8 | test "just" do 9 | assert just(1) 10 | == %Maybe{just: 1, nothing: false} 11 | assert just(1) |> fmap(&(&1+1)) 12 | == %Maybe{just: 2, nothing: false} 13 | end 14 | 15 | test "left" do 16 | assert nothing() 17 | == %Maybe{just: nil, nothing: true} 18 | assert nothing() |> fmap(fn _ -> nothing() end) 19 | == %Maybe{just: nil, nothing: true} 20 | assert just(1) |> fmap(fn _ -> nothing() end) 21 | == %Maybe{just: %Maybe{just: nil, nothing: true}, nothing: false} 22 | end 23 | 24 | test "laws" do 25 | f = just(1) 26 | id = fn x -> x end 27 | p = fn x -> x + 1 end 28 | q = fn x -> x + 2 end 29 | 30 | assert f |> fmap(id) == id.(f) 31 | assert f |> fmap(q <|> p) == f |> fmap(p) |> fmap(q) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/control/functor/writer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Functor.WriterTest do 2 | use ExUnit.Case 3 | alias Data.Writer 4 | import Writer 5 | import Control.Helpers 6 | import Control.Functor 7 | 8 | test "value" do 9 | assert value(1) 10 | == %Writer{value: 1, log: []} 11 | assert value(1) |> fmap(&(&1+1)) 12 | == %Writer{value: 2, log: []} 13 | end 14 | 15 | test "log" do 16 | assert value(1) |> log("msg 1") 17 | == %Writer{value: 1, log: ["msg 1"]} 18 | assert value(1) |> log("msg 1") |> fmap(fn _ -> struct(Writer) |> log("msg 2") end) 19 | == %Writer{value: %Writer{value: nil, log: ["msg 2"]}, log: ["msg 1"]} 20 | assert value(1) |> fmap(fn _ -> struct(Writer) |> log("msg 2") end) 21 | == %Writer{value: %Writer{value: nil, log: ["msg 2"]}, log: []} 22 | end 23 | 24 | test "laws" do 25 | f = value(1) 26 | id = fn x -> x end 27 | p = fn x -> x + 1 end 28 | q = fn x -> x + 2 end 29 | 30 | assert f |> fmap(id) == id.(f) 31 | assert f |> fmap(q <|> p) == f |> fmap(p) |>fmap(q) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/control/helpers_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.HelpersTest do 2 | use ExUnit.Case 3 | alias Data.Maybe 4 | import Maybe 5 | import Control.Helpers 6 | 7 | test "~>>" do 8 | assert nothing() ~>> fn _ -> nothing() end 9 | == nothing() 10 | assert nothing() ~>> &(just(&1 * 2)) 11 | == nothing() 12 | 13 | assert just(2) ~>> fn _ -> nothing() end 14 | == nothing() 15 | assert just(2) ~>> &(just(&1 * 2)) 16 | == just(4) 17 | end 18 | 19 | test "<|>" do 20 | f = fn x -> x + 2 end 21 | g = fn x -> x / 2 end 22 | 23 | assert (f <|> g).(2) == f.(g.(2)) 24 | assert (f <|> g <|> &h/1).(2) == f.(g.(h(2))) 25 | end 26 | defp h(x), do: x - 2 27 | end 28 | -------------------------------------------------------------------------------- /test/control/monad/either_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Monad.EitherTest do 2 | use ExUnit.Case 3 | alias Data.Either 4 | import Either 5 | import Control.Helpers 6 | import Control.Monad 7 | 8 | test "bind" do 9 | assert left("error 1") |> bind(fn _ -> left("error 2") end) 10 | == left("error 1") 11 | assert left("error 1") |> bind(&(right(&1 * 2))) 12 | == left("error 1") 13 | 14 | assert right(2) |> bind(fn _ -> left("error") end) 15 | == left("error") 16 | assert right(2) |> bind(&(right(&1 * 2))) 17 | == right(4) 18 | end 19 | 20 | test "~>>" do 21 | assert left("error 1") ~>> fn _ -> left("error 2") end 22 | == left("error 1") 23 | assert left("error 1") ~>> &(right(&1 * 2)) 24 | == left("error 1") 25 | 26 | assert right(2) ~>> fn _ -> left("error") end 27 | == left("error") 28 | assert right(2) ~>> &(right(&1 * 2)) 29 | == right(4) 30 | end 31 | 32 | test "laws" do 33 | x = 1 34 | m = right(1) 35 | f = fn x -> right(x + 2) end 36 | g = fn x -> right(x + 3) end 37 | 38 | assert %Either{} |> return(x) ~>> f == f.(x) 39 | assert m |> return == m 40 | assert (m ~>> f) ~>> g == m ~>> &(f.(&1) ~>> g) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/control/monad/list_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Monad.ListTest do 2 | use ExUnit.Case 3 | import Control.Helpers 4 | import Control.Monad 5 | 6 | test "bind" do 7 | assert [] |> bind(fn _ -> [] end) 8 | == [] 9 | assert [] |> bind(&([&1 * 2])) 10 | == [] 11 | 12 | assert [2] |> bind(fn _ -> [] end) 13 | == [] 14 | assert [2] |> bind(&([&1 * 2])) 15 | == [4] 16 | end 17 | 18 | test "~>>" do 19 | assert [] ~>> fn _ -> [] end 20 | == [] 21 | assert [] ~>> &([&1 * 2]) 22 | == [] 23 | 24 | assert [2] ~>> fn _ -> [] end 25 | == [] 26 | assert [2] ~>> &([&1 * 2]) 27 | == [4] 28 | end 29 | 30 | test "laws" do 31 | x = 1 32 | m = [1] 33 | f = fn x -> [x + 2] end 34 | g = fn x -> [x + 3] end 35 | 36 | assert [] |> return(x) ~>> f == f.(x) 37 | assert m |> return == m 38 | assert (m ~>> f) ~>> g == m ~>> &(f.(&1) ~>> g) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/control/monad/maybe_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Monad.MaybeTest do 2 | use ExUnit.Case 3 | alias Data.Maybe 4 | import Maybe 5 | import Control.Helpers 6 | import Control.Monad 7 | 8 | test "bind" do 9 | assert nothing() |> bind(fn _ -> nothing() end) 10 | == nothing() 11 | assert nothing() |> bind(&(just(&1 * 2))) 12 | == nothing() 13 | 14 | assert just(2) |> bind(fn _ -> nothing() end) 15 | == nothing() 16 | assert just(2) |> bind(&(just(&1 * 2))) 17 | == just(4) 18 | end 19 | 20 | test "~>>" do 21 | assert nothing() ~>> fn _ -> nothing() end 22 | == nothing() 23 | assert nothing() ~>> &(just(&1 * 2)) 24 | == nothing() 25 | 26 | assert just(2) ~>> fn _ -> nothing() end 27 | == nothing() 28 | assert just(2) ~>> &(just(&1 * 2)) 29 | == just(4) 30 | end 31 | 32 | test "laws" do 33 | x = 1 34 | m = just(1) 35 | f = fn x -> just(x + 2) end 36 | g = fn x -> just(x + 3) end 37 | 38 | assert %Maybe{} |> return(x) ~>> f == f.(x) 39 | assert m |> return == m 40 | assert (m ~>> f) ~>> g == m ~>> &(f.(&1) ~>> g) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/control/monad/writer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Monad.WriterTest do 2 | use ExUnit.Case 3 | alias Data.Writer 4 | import Writer 5 | import Control.Helpers 6 | import Control.Monad 7 | 8 | test "log" do 9 | assert value(2) |> log("msg 1") 10 | == %Writer{value: 2, log: ["msg 1"]} 11 | assert value(2) |> log("msg 1") |> bind(fn x -> %Writer{} |> return(x) end) 12 | == value(2) |> log("msg 1") 13 | assert value(2) |> log("msg 1") |> bind(&(%Writer{} |> log("msg 2") |> return(&1 * 2))) 14 | == value(4) |> log("msg 1") |> log("msg 2") 15 | end 16 | 17 | test "bind" do 18 | assert value(2) |> bind(fn x -> %Writer{} |> return(x) end) 19 | == value(2) 20 | assert value(2) |> bind(&(%Writer{} |> return(&1 * 2))) 21 | == value(4) 22 | end 23 | 24 | test "~>>" do 25 | assert value(2) ~>> fn x -> %Writer{} |> return(x) end 26 | == value(2) 27 | assert value(2) ~>> fn x -> %Writer{} |> return(x * 2) end 28 | == value(4) 29 | end 30 | 31 | test "laws" do 32 | x = 1 33 | m = value(2) 34 | f = fn x -> %Writer{} |> return(x + 2) end 35 | g = fn x -> %Writer{} |> return(x + 3) end 36 | 37 | assert %Writer{} |> return(x) ~>> f == f.(x) 38 | assert m |> return == m 39 | assert (m ~>> f) ~>> g == m ~>> &(f.(&1) ~>> g) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/control/monoid/list_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Control.Monoid.ListTest do 2 | use ExUnit.Case 3 | import Control.Monoid 4 | 5 | test "mempty" do 6 | assert mempty([]) == [] 7 | end 8 | 9 | test "mappend" do 10 | assert [] |> mappend([]) == [] 11 | assert [1,2,3] |> mappend([]) == [1,2,3] 12 | assert [1,2,3] |> mappend([4,5,6]) == [1,2,3,4,5,6] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/control_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ControlTest do 2 | use ExUnit.Case 3 | doctest Control 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------