Skip to content

Commit

Permalink
Add Pointfree (unary only for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooklyn Zelenka committed Sep 12, 2016
1 parent bf072a9 commit c1498bb
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 24 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
```elixir

def deps do
[{:quark, "~> 2.1"}]
[{:quark, "~> 2.2"}]
end

defmodule MyModule do
Expand Down Expand Up @@ -137,6 +137,20 @@ minus.(10).(2).(1)

```

## Pointfree
Allows defining functions as straight function composition (ie: no need to state the argument).
Provides a clean, composable named functions. Also doubles as an aliasing device.

```elixir
defmodule Foo do
use Quark.Pointfree
defx foo, do: Enum.sum |> fn x -> x + 1 end.()
end

Foo.foo([1,2,3])
#=> 7
```

## Compose
Compose functions to do convenient partial applications.
Versions for composing left-to-right and right-to-left are provided, but the
Expand Down
1 change: 1 addition & 0 deletions lib/quark.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Quark do
import unquote(__MODULE__)
use Quark.Curry
use Quark.Partial
use Quark.Pointfree
end
end

Expand Down
47 changes: 41 additions & 6 deletions lib/quark/compose.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ defmodule Quark.Compose do
provides the opposite in the form of `*_forward` functions.
"""

import Quark.SKI

use Quark.Partial
alias Quark.SKI
use Quark.Curry

@doc ~S"""
Function composition
Expand All @@ -30,7 +32,13 @@ defmodule Quark.Compose do
"""
@spec compose(fun, fun) :: any
defdelegate compose(g, f), to: Quark.BCKW, as: :b
def compose(g, f) do
fn x ->
x
|> curry(f).()
|> curry(g).()
end
end

@doc ~S"""
Function composition, from the tail of the list to the head
Expand All @@ -43,8 +51,7 @@ defmodule Quark.Compose do
"""
@spec compose([fun]) :: fun
def compose(func_list), do: List.foldr(func_list, &SKI.id/1, &compose(&1,&2))
def compose(), do: &compose/1
defpartial compose(func_list), do: func_list |> List.foldr(&id/1, &compose/2)

@doc ~S"""
Infix compositon operator
Expand Down Expand Up @@ -76,8 +83,36 @@ defmodule Quark.Compose do
"""
@spec compose_forward(fun, fun) :: fun
defpartial compose_forward(f,g), do: &(g.(f.(&1)))
defpartial compose_forward(f,g) do
fn x ->
x
|> curry(f).()
|> curry(g).()
end
end

@doc ~S"""
Infix "forward" compositon operator
## Examples
iex> sum_plus_one = (&Enum.sum/1) <~> fn x -> x + 1 end
iex> sum_plus_one.([1,2,3])
7
iex> x200 = (&(&1 * 2)) <~> (&(&1 * 10)) <~> (&(&1 * 10))
iex> x200.(5)
1000
iex> add_one = &(&1 + 1)
iex> piped = [1,2,3] |> Enum.sum |> add_one.()
iex> composed = [1,2,3] |> ((&Enum.sum/1) <~> add_one).()
iex> piped == composed
true
"""
@spec fun <~> fun :: fun
def f <~> g, do: compose_forward(f, g)

@doc ~S"""
Compose functions, from the head of the list of functions. The is the reverse
Expand All @@ -93,6 +128,6 @@ defmodule Quark.Compose do
"""
@spec compose_list_forward([fun]) :: fun
defpartial compose_list_forward(func_list) do
func_list |> Enum.reduce(&SKI.id/1, &compose/2)
func_list |> Enum.reduce(&id/1, &compose/2)
end
end
3 changes: 1 addition & 2 deletions lib/quark/curry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ defmodule Quark.Curry do
@spec curry(fun, integer, [any]) :: fun
defp curry(fun, 0, arguments), do: apply(fun, Enum.reverse(arguments))
defp curry(fun, arity, arguments) do
import Quark.Sequence, only: [pred: 1]
fn arg -> curry(fun, pred(arity), [arg | arguments]) end
fn arg -> curry(fun, arity - 1, [arg | arguments]) end
end

@doc ~S"""
Expand Down
66 changes: 66 additions & 0 deletions lib/quark/pointfree.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
defmodule Quark.Pointfree do
@moduledoc ~S"""
Allows defining functions as straight function composition
(ie: no need to state the argument).
Provides a clean, composable named functions
"""

defmacro __using__(_) do
quote do
require unquote(__MODULE__)
import unquote(__MODULE__)
end
end

@doc ~S"""
Define a unary function with an implied subject
## Examples
iex> defmodule Foo do
...> use Quark.Pointfree
...> defx foo(), do: Enum.sum |> fn x -> x + 1 end.()
...> end
...> Foo.foo([1,2,3])
7
iex> defmodule Bar do
...> use Quark.Pointfree
...> defx bar, do: Enum.sum |> fn x -> x + 1 end.()
...> end
...> Bar.bar([1,2,3])
7
"""
defmacro defx(head, do: body) do
{fun_name, ctx, _} = head

quote do
def unquote({fun_name, ctx, [{:subject, [], Elixir}]}) do
unquote({:subject, [], Elixir}) |> unquote(body)
end
end
end

@doc ~S"""
Define a private unary function with an implied subject
## Examples
defmodule Foo do
use Quark.Pointfree
defxp foo(), do: Enum.sum |> fn x -> x + 1 end.()
end
"""
defmacro defxp(head, do: body) do
{fun_name, ctx, []} = head

quote do
defp unquote({fun_name, ctx, [{:subject, [], Elixir}]}) do
unquote({:subject, [], Elixir}) |> unquote(body)
end
end
end
end
48 changes: 37 additions & 11 deletions lib/quark/sequence.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
defprotocol Quark.Sequence do
@moduledoc ~S"""
A protocol for stepping through ordered enumerables
"""
@moduledoc "A protocol for stepping through ordered enumerables"

@doc ~S"""
The beginning of the sequence.
Expand All @@ -10,8 +8,8 @@ defprotocol Quark.Sequence do
## Examples
iex> origin(9)
0
origin(9)
#=> 0
"""
@spec origin(any) :: any
Expand All @@ -25,10 +23,10 @@ defprotocol Quark.Sequence do
## Examples
iex> succ(1)
2
#=> 2
iex> 10 |> origin |> succ |> succ
2
#=> 2
"""
@spec succ(any) :: any
Expand All @@ -41,19 +39,47 @@ defprotocol Quark.Sequence do
## Examples
iex> pred(10)
9
pred(10)
#=> 9
iex> 42 |> origin |> pred |> pred
-2
42 |> origin |> pred |> pred
#=> -2
"""
@spec pred(any) :: any
def pred(element)
end

defimpl Quark.Sequence, for: Integer do
@doc ~S"""
## Examples
iex> origin(9)
0
"""
def origin(num), do: 0

@doc ~S"""
## Examples
iex> succ(1)
2
iex> 10 |> origin |> succ |> succ
2
"""
def succ(num), do: num + 1

@doc ~S"""
## Examples
iex> pred(10)
9
iex> 42 |> origin |> pred |> pred
-2
"""
def pred(num), do: num - 1
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Quark.Mixfile do
name: "Quark",

description: "Common combinators for Elixir",
version: "2.1.1",
version: "2.2.0",
elixir: "~> 1.3",

package: [
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"credo": {:hex, :credo, "0.4.11", "03a64e9d53309b7132556284dda0be57ba1013885725124cfea7748d740c6170", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]},
"dialyxir": {:hex, :dialyxir, "0.3.5", "eaba092549e044c76f83165978979f60110dc58dd5b92fd952bf2312f64e9b14", [:mix], []},
"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.13.1", "658dbfc8cc5b0fac192f0f3254efe66ee6294200804a291549e0aeb052053bba", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"inch_ex": {:hex, :inch_ex, "0.5.3", "39f11e96181ab7edc9c508a836b33b5d9a8ec0859f56886852db3d5708889ae7", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}}
6 changes: 4 additions & 2 deletions test/quark_test.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
defmodule QuarkTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false # true

doctest Quark, import: true

doctest Quark.BCKW, import: true
doctest Quark.Compose, import: true
doctest Quark.Curry, import: true
doctest Quark.FixedPoint, import: true
doctest Quark.M, import: true
doctest Quark.Partial, import: true
doctest Quark.Sequence, import: true
doctest Quark.Pointfree, import: true
doctest Quark.Sequence.Integer, import: true
doctest Quark.SKI, import: true
end

0 comments on commit c1498bb

Please sign in to comment.