diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1309577 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "26.0.2" + gleam-version: "1.3.1" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/README.md b/README.md new file mode 100644 index 0000000..b08a438 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# recursive + +[![Package Version](https://img.shields.io/hexpm/v/recursive)](https://hex.pm/packages/recursive) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/recursive/) + +```sh +gleam add recursive +``` + +```gleam +import gleam/io +import recursive + +pub fn main() { + // Factorial + io.debug({ + use it, f <- recursive.use_fix(10) + case it { + 0 -> 1 + _ -> it * f(it - 1) + } + }) + let fac = recursive.fix(fn (it, f) { + case it { + 0 -> 1 + _ -> it * f(it - 1) + } + }) + + // Tail recursive factorial + io.debug({ + use it, res, f <- recursive.use_fix2(10, 1) + case it { + 0 -> res + _ -> f(it - 1, res * it) + } + }) + let memo_fac = recursive.fix2(fn (it, res, f) { + case it { + 0 -> res + _ -> f(it - 1, res * it) + } + }) + + // Tail recursive fibonacci + io.debug({ + use it, curr, prev, f <- recursive.use_fix3(9, 1, 0) + case it { + 0 -> curr + _ -> f(it - 1, curr + prev, curr) + } + }) + let fib = recursive.fix3(fn (it, curr, prev, f) { + case it { + 0 -> curr + _ -> f(it - 1, curr + prev, curr) + } + }) +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam test +``` diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..b548cf0 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,10 @@ +name = "recursive" +version = "1.0.0" + +description = "A Fixed-point combinator implementation & utility functions for better DX when using it" +licences = ["MIT"] +repository = { type = "github", user = "olian04", repo = "gleam_recursive" } +gleam = ">= 1.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..ab37ef9 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,10 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, + { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, +] + +[requirements] +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/recursive.gleam b/src/recursive.gleam new file mode 100644 index 0000000..8b17c04 --- /dev/null +++ b/src/recursive.gleam @@ -0,0 +1,132 @@ +/// Raw fixed-point combinator. +/// Not intended to be used standalone +pub fn rec(f) { + f(fn() { rec(f) }) +} + +/// Fixed-point combinator. +/// Allows anonymous functions to run recursively +/// ```gleam +/// let factorial = recursive.fix(fn (it, f) { +/// case it { +/// 0 -> 1 +/// 1 -> 1 +/// _ -> it * f(it - 1) +/// } +/// }) +/// io.debug(factorial(10)) +/// ``` +pub fn fix(cb) { + rec(fn(f) { fn(a) { cb(a, cb(_, f())) } }) +} + +/// Two argument Fixed-point combinator. +/// Allows anonymous functions to run recursively +/// ```gleam +/// let factorial = recursive.fix2(fn (it, res, f) { +/// case it { +/// 0 -> res +/// 1 -> res +/// _ -> f(it - 1, res * it) +/// } +/// }) +/// io.debug(factorial(10, 1)) +/// ``` +pub fn fix2(cb) { + rec(fn(f) { fn(a, b) { cb(a, b, fn(a, b) { cb(a, b, f()) }) } }) +} + +/// Three argument Fixed-point combinator. +/// Allows anonymous functions to run recursively +/// ```gleam +/// let fibonacci = recursive.fix3(fn (it, curr, prev, f) { +/// case it { +/// 0 -> curr +/// _ -> f(it - 1, curr + prev, curr) +/// } +/// }) +/// io.debug(fibonacci(9, 1, 0)) +/// ``` +pub fn fix3(cb) { + rec(fn(f) { fn(a, b, c) { cb(a, b, c, fn(a, b, c) { cb(a, b, c, f()) }) } }) +} + +/// Four argument Fixed-point combinator. +/// Allows anonymous functions to run recursively +pub fn fix4(cb) { + rec(fn(f) { + fn(a, b, c, d) { cb(a, b, c, d, fn(a, b, c, d) { cb(a, b, c, d, f()) }) } + }) +} + +/// Five argument Fixed-point combinator. +/// Allows anonymous functions to run recursively +pub fn fix5(cb) { + rec(fn(f) { + fn(a, b, c, d, e) { + cb(a, b, c, d, e, fn(a, b, c, d, e) { cb(a, b, c, d, e, f()) }) + } + }) +} + +/// Usable Fixed-point combinator. +/// Allows any block to run recursively +/// ```gleam +/// // Factorial +/// io.debug({ +/// use it, f <- recursive.use_fix(10) +/// case it { +/// 0 -> 1 +/// 1 -> 1 +/// _ -> it * f(it - 1) +/// } +/// }) +/// ``` +pub fn use_fix(initial, cb) { + fix(cb)(initial) +} + +/// Two argument Fixed-point combinator. +/// Allows any block to run recursively +/// ```gleam +/// // Tail recursive factorial +/// io.debug({ +/// use it, res, f <- recursive.use_fix2(10, 1) +/// case it { +/// 0 -> res +/// 1 -> res +/// _ -> f(it - 1, res * it) +/// } +/// }) +/// ``` +pub fn use_fix2(a, b, cb) { + fix2(cb)(a, b) +} + +/// Three argument Fixed-point combinator. +/// Allows any block to run recursively +/// ```gleam +/// // Fibonacci +/// io.debug({ +/// use it, curr, prev, f <- recursive.use_fix3(9, 1, 0) +/// case it { +/// 0 -> curr +/// _ -> f(it - 1, curr + prev, curr) +/// } +/// }) +/// ``` +pub fn use_fix3(a, b, c, cb) { + fix3(cb)(a, b, c) +} + +/// Four argument Fixed-point combinator. +/// Allows any block to run recursively +pub fn use_fix4(a, b, c, d, cb) { + fix4(cb)(a, b, c, d) +} + +/// Five argument Fixed-point combinator. +/// Allows any block to run recursively +pub fn use_fix5(a, b, c, d, e, cb) { + fix5(cb)(a, b, c, d, e) +} diff --git a/test/recursive_test.gleam b/test/recursive_test.gleam new file mode 100644 index 0000000..8e831ef --- /dev/null +++ b/test/recursive_test.gleam @@ -0,0 +1,115 @@ +import gleeunit +import gleeunit/should +import recursive + +pub fn main() { + gleeunit.main() +} + +fn fac(it, f) { + case it { + 0 -> 1 + 1 -> 1 + _ -> it * f(it - 1) + } +} + +fn memo_fac(it, res, f) { + case it { + 0 -> res + 1 -> res + _ -> f(it - 1, res * it) + } +} + +fn fib(it, curr, prev, f) { + case it { + 0 -> curr + _ -> f(it - 1, curr + prev, curr) + } +} + +fn sum(a, b, c, d, f) { + case a, b, c, d { + 0, 0, 0, 0 -> 0 + 0, 0, 0, v -> 1 + f(0, 0, 0, v - 1) + 0, 0, v, _ -> 1 + f(0, 0, v - 1, d) + 0, v, _, _ -> 1 + f(0, v - 1, c, d) + v, _, _, _ -> 1 + f(v - 1, b, c, d) + } +} + +fn memo_sum(a, b, c, d, res, f) { + case a, b, c, d { + 0, 0, 0, 0 -> res + 0, 0, 0, v -> f(0, 0, 0, v - 1, res + 1) + 0, 0, v, _ -> f(0, 0, v - 1, d, res + 1) + 0, v, _, _ -> f(0, v - 1, c, d, res + 1) + v, _, _, _ -> f(v - 1, b, c, d, res + 1) + } +} + +pub fn fix_test() { + recursive.fix(fac)(10) + |> should.equal(3_628_800) +} + +pub fn fix2_test() { + recursive.fix2(memo_fac)(10, 1) + |> should.equal(3_628_800) +} + +pub fn fix3_test() { + recursive.fix3(fib)(9, 1, 0) + |> should.equal(55) +} + +pub fn fix4_test() { + recursive.fix4(sum)(1, 2, 3, 4) + |> should.equal(10) +} + +pub fn fix5_test() { + recursive.fix5(memo_sum)(1, 2, 3, 4, 0) + |> should.equal(10) +} + +pub fn use_fix_test() { + { + use it, f <- recursive.use_fix(10) + fac(it, f) + } + |> should.equal(3_628_800) +} + +pub fn use_fix2_test() { + { + use it, res, f <- recursive.use_fix2(10, 1) + memo_fac(it, res, f) + } + |> should.equal(3_628_800) +} + +pub fn use_fix3_test() { + { + use it, curr, prev, f <- recursive.use_fix3(9, 1, 0) + fib(it, curr, prev, f) + } + |> should.equal(55) +} + +pub fn use_fix4_test() { + { + use a, b, c, d, f <- recursive.use_fix4(1, 2, 3, 4) + sum(a, b, c, d, f) + } + |> should.equal(10) +} + +pub fn use_fix5_test() { + { + use a, b, c, d, res, f <- recursive.use_fix5(1, 2, 3, 4, 0) + memo_sum(a, b, c, d, res, f) + } + |> should.equal(10) +}