Skip to content

Commit

Permalink
Benchmarks & refactors (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
expede committed Aug 5, 2017
1 parent ea46dea commit fcc66ef
Show file tree
Hide file tree
Showing 80 changed files with 4,581 additions and 139 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -7,3 +7,5 @@ erl_crash.dump
*.beam
.DS_Store
/docs/all.json
/bench/snapshots
/bench/graphs
22 changes: 14 additions & 8 deletions README.md
@@ -1,7 +1,7 @@
![](https://github.com/expede/witchcraft/raw/master/brand/Wordmark/PNG/WC-wordmark-lrg@2x.png)

`Witchcraft` is a library providing common algebraic and categorical abstractions to Elixir.
Monoids, functors, monads, arrows, categories, and other dark magic.
Monoids, functors, monads, arrows, categories, and other dark magic right at your fingertips.

[![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)

Expand All @@ -24,7 +24,7 @@ Monoids, functors, monads, arrows, categories, and other dark magic.

```elixir
def deps do
[{:witchcraft, "1.0.0-beta"}]
[{:witchcraft, "1.0.0-beta.4"}]
end

# ...
Expand Down Expand Up @@ -110,21 +110,27 @@ both `liftA*` and `liftM*`, and so on.

### Import Chains

It is very common to want everything in a chain. You can import the entire chain
with `use`. For example, you can import the entire library with:
It is very common to want to import a class and all of its dependencies.
You can do this with `use`. For example, you can import the entire library with:

```elixir
use Witchcraft.Monad
use Witchcraft
```

Or import a module plus all others that it depends on:

```elixir
use Witchcraft.Chain
```

Any options that you pass to `use` will be propagated all the way down the chain
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
if you would like to skip all overrides, pass `override_kernel: false` as an option:

```elixir
use Witchcraft, override_kernel: false
Expand Down Expand Up @@ -210,7 +216,7 @@ More reference instances are available in [`Algae`](https://github.com/expede/al
## Prior Art and Further Reading

This library draws heavy inspiration from mathematics, other laguages,
other Elixir libraries. We would be a shame not to mention them here.
and other Elixir libraries. We would be a shame not to mention them here.
There is much, much more out there, but these are the our highlights and inspirations.

The [`Monad`](https://hexdocs.pm/monad/Monad.html) library predates `Witchcraft`
Expand Down
29 changes: 29 additions & 0 deletions bench/witchcraft/applicative/function_bench.exs
@@ -0,0 +1,29 @@
defmodule Witchcraft.Applicative.FunctionBench do
use Benchfella
use Witchcraft.Applicative

#########
# Setup #
#########

# ---------- #
# Data Types #
# ---------- #

def fun(x), do: "#{inspect x}-#{inspect x}"

# -------------- #
# Test Functions #
# -------------- #

defp twice(f), do: fn x -> x |> f.() |> f.() end
defp to_fun(x), do: of(&fun/1).(x)

###############
# Applicative #
###############

bench "of/1", do: to_fun(42)
bench "of/2", do: of(&fun/1, &twice/1)

end
29 changes: 29 additions & 0 deletions bench/witchcraft/applicative/list_bench.exs
@@ -0,0 +1,29 @@
defmodule Witchcraft.Applicative.ListBench do
use Benchfella
use Witchcraft.Applicative

#########
# Setup #
#########

# ---------- #
# Data Types #
# ---------- #

@to_wrap 11..100 |> Enum.to_list() |> Enum.shuffle()
@list 99..999 |> Enum.to_list() |> Enum.shuffle()

# -------------- #
# Test Functions #
# -------------- #

defp to_list(x), do: of(@list).(x)

###############
# Applicative #
###############

bench "of/1", do: to_list(@to_wrap)
bench "of/2", do: of(@list, @to_wrap)

end
29 changes: 29 additions & 0 deletions bench/witchcraft/applicative/tuple_bench.exs
@@ -0,0 +1,29 @@
defmodule Witchcraft.Applicative.TupleBench do
use Benchfella
use Witchcraft.Applicative

#########
# Setup #
#########

# ---------- #
# Data Types #
# ---------- #

@to_wrap 11..100 |> Enum.to_list() |> Enum.shuffle()
@tuple 99..999 |> Enum.to_list() |> Enum.shuffle() |> List.to_tuple()

# -------------- #
# Test Functions #
# -------------- #

defp to_tuple(x), do: of(@tuple).(x)

###############
# Applicative #
###############

bench "of/1", do: to_tuple(@to_wrap)
bench "of/2", do: of(@tuple, @to_wrap)

end
172 changes: 172 additions & 0 deletions bench/witchcraft/apply/function_bench.exs
@@ -0,0 +1,172 @@
defmodule Witchcraft.Apply.FunBench do
use Benchfella
use Witchcraft.Apply

#########
# Setup #
#########

# ---------- #
# Data Types #
# ---------- #

defp fun(x, y, z), do: x + y * z
defp fun(z), do: fn x -> fn y -> x + y + z end end

defp slow_fun(z) do
Process.sleep(20)
fn(x, y) ->
x + y * z
end
end

defp slow_fun(x, y, z) do
Process.sleep(20)
x + y * z
end

#########
# Apply #
#########

# ==== #
# Data #
# ==== #

bench "data convey/2", do: fn x -> fun(x) end |> convey(fn x -> fun(x) end)
bench "data ap/2", do: fn x -> fun(x) end |> ap(fn x -> fun(x) end)

bench "data <<~/2", do: fn x -> fun(x) end <<~ fn x -> fun(x) end
bench "data <<~/2", do: fn x -> fun(x) end <<~ fn x -> fun(x) end

bench "data ~>>/2", do: fn x -> fun(x) end ~>> fn x -> fun(x) end
bench "data ~>>/2", do: fn x -> fun(x) end ~>> fn x -> fun(x) end

bench "data provide/2", do: fn x -> fun(x) end |> provide(fn x -> fun(x) end)
bench "data supply/2", do: fn x -> fun(x) end |> supply(fn x -> fun(x) end)

bench "data lift/3", do: lift(fn x -> fun(x) end, fn x -> fun(x) end, &+/2)
bench "data lift/4", do: lift(fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y, z) -> x + y + z end)
bench "data lift/5", do: lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(w, x, y, z) -> w + x + y + z end)

bench "data over/3", do: over(&+/2, fn x -> fun(x) end, fn x -> fun(x) end)
bench "data over/4", do: over(fn(x, y, z) -> x + y + z end, fn x -> fun(x) end, fn x -> fun(x) end)
bench "data over/5", do: over(fn(w, x, y, z) -> w + x + y + z end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)

bench "following/2", do: fn x -> fun(x) end |> following(fn x -> fun(x) end)
bench "then/2", do: fn x -> fun(x) end |> then(fn x -> fun(x) end)

# ----- #
# Async #
# ----- #

bench "data async_convey/2", do: fn x -> fun(x) end |> async_convey(fn x -> fun(x) end)
bench "data async_ap/2", do: fn x -> fun(x) end |> async_ap(fn x -> fun(x) end)

bench "async_lift/3", do: async_lift(fn x -> fun(x) end, fn x -> fun(x) end, &+/2)
bench "async_lift/4", do: async_lift(fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y, z) -> x + y + z end)
bench "async_lift/5", do: async_lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(w, x, y, z) -> w + x + y + z end)

bench "async_over/3", do: async_over(&+/2, fn x -> fun(x) end, fn x -> fun(x) end)
bench "async_over/4", do: async_over(fn(x, y, z) -> x + y + z end, fn x -> fun(x) end, fn x -> fun(x) end)
bench "async_over/5", do: async_over(fn(w, x, y, z) -> w + x + y + z end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)

bench "!!! convey/2", do: fn x -> fun(x) end |> convey(fn y -> slow_fun(y) end)
bench "!!! ap/2", do: fn y -> slow_fun(y) end |> ap(fn x -> fun(x) end)

bench "!!! lift/3" do
lift(fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y) ->
Process.sleep(20)
x + y
end)
end

bench "!!! lift/4" do
lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y, z) ->
Process.sleep(20)
x + y + z
end)
end

# also very slow, due to exponential complexity of multiple fun dimensions
# 50^4 = 6_250_000 items to process
bench "!!! lift/5" do
lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(w, x, y, z) ->
Process.sleep(20)
w + x + y + z
end)
end

bench "!!! over/3" do
fn(x, y) ->
Process.sleep(20)
x + y
end
|> over(fn x -> fun(x) end, fn x -> fun(x) end)
end

bench "!!! over/4" do
fn(x, y, z) ->
Process.sleep(20)
x + y + z
end
|> over(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)
end

# So slow
bench "!!! over/5" do
fn(w, x, y, z) ->
Process.sleep(20)
w + x + y + z
end
|> over(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)
end

bench "!!! async_convey/2", do: fn x -> fun(x) end |> async_convey(fn y -> slow_fun(y) end)
bench "!!! async_ap/2", do: fn y -> slow_fun(y) end |> async_ap(fn x -> fun(x) end)

bench "!!! async_lift/3" do
async_lift(fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y) ->
Process.sleep(20)
x + y
end)
end

bench "!!! async_lift/4" do
async_lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(x, y, z) ->
Process.sleep(20)
x + y + z
end)
end

bench "!!! async_lift/5" do
async_lift(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn(w, x, y, z) ->
Process.sleep(20)
w + x + y + z
end)
end

bench "!!! async_over/3" do
fn(x, y) ->
Process.sleep(20)
x + y
end
|> async_over(fn x -> fun(x) end, fn x -> fun(x) end)
end

bench "!!! async_over/4" do
fn(x, y, z) ->
Process.sleep(20)
x + y + z
end
|> async_over(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)
end

bench "!!! async_over/5" do
fn(w, x, y, z) ->
Process.sleep(20)
w + x + y + z
end
|> async_over(fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end, fn x -> fun(x) end)
end
end

0 comments on commit fcc66ef

Please sign in to comment.