Put your function in a loop until the cycle (pun intended) breaks
gleam

release: bump v1.0.0

+322
+1
.envrc
··· 1 + use flake
+5
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + /.direnv
+24
LICENSE
··· 1 + This is free and unencumbered software released into the public domain. 2 + 3 + Anyone is free to copy, modify, publish, use, compile, sell, or 4 + distribute this software, either in source code form or as a compiled 5 + binary, for any purpose, commercial or non-commercial, and by any 6 + means. 7 + 8 + In jurisdictions that recognize copyright laws, the author or authors 9 + of this software dedicate any and all copyright interest in the 10 + software to the public domain. We make this dedication for the benefit 11 + of the public at large and to the detriment of our heirs and 12 + successors. We intend this dedication to be an overt act of 13 + relinquishment in perpetuity of all present and future rights to this 14 + software under copyright law. 15 + 16 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 + OTHER DEALINGS IN THE SOFTWARE. 23 + 24 + For more information, please refer to <https://unlicense.org/>
+35
README.md
··· 1 + # cycle 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/cycle)](https://hex.pm/packages/cycle) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/cycle/) 5 + 6 + ```sh 7 + gleam add cycle@1 8 + ``` 9 + ```gleam 10 + import cycle 11 + 12 + pub fn main() -> Nil { 13 + echo cycle.start(with: #([], 0), run: fn(state) { 14 + let #(is, i) = state 15 + case i < 5 { 16 + True -> { 17 + let i = i + 1 18 + cycle.continue(#(list.prepend(is, i), i)) 19 + } 20 + _ -> cycle.stop(is) 21 + } 22 + }) 23 + 24 + // output: [5, 4, 3, 2, 1] 25 + } 26 + ``` 27 + 28 + Further documentation can be found at <https://hexdocs.pm/cycle>. 29 + 30 + ## Development 31 + 32 + ```sh 33 + gleam run # Run the cycle 34 + gleam test # Run the tests 35 + ```
+92
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flakelight": { 4 + "inputs": { 5 + "nixpkgs": [ 6 + "nixpkgs" 7 + ] 8 + }, 9 + "locked": { 10 + "lastModified": 1771852612, 11 + "narHash": "sha256-QyuS1rpMQgwguwMZbi2PLhK37XaSOgQa3EIKbHzcjAU=", 12 + "owner": "nix-community", 13 + "repo": "flakelight", 14 + "rev": "62f0f59c4a26648c133abdff88019a871fd32ed8", 15 + "type": "github" 16 + }, 17 + "original": { 18 + "owner": "nix-community", 19 + "repo": "flakelight", 20 + "type": "github" 21 + } 22 + }, 23 + "flakelight-treefmt": { 24 + "inputs": { 25 + "flakelight": [ 26 + "flakelight" 27 + ], 28 + "treefmt-nix": "treefmt-nix" 29 + }, 30 + "locked": { 31 + "lastModified": 1771938406, 32 + "narHash": "sha256-ApWBFFb/kll0k96Yf56PXzz2kEV3GvY8UsDQHvWb2o4=", 33 + "owner": "m15a", 34 + "repo": "flakelight-treefmt", 35 + "rev": "1a38d202e5599bb464a8c135bcc2088dc1bdcbdc", 36 + "type": "github" 37 + }, 38 + "original": { 39 + "owner": "m15a", 40 + "repo": "flakelight-treefmt", 41 + "type": "github" 42 + } 43 + }, 44 + "nixpkgs": { 45 + "locked": { 46 + "lastModified": 1772198003, 47 + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", 48 + "owner": "NixOS", 49 + "repo": "nixpkgs", 50 + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", 51 + "type": "github" 52 + }, 53 + "original": { 54 + "owner": "NixOS", 55 + "ref": "nixos-unstable", 56 + "repo": "nixpkgs", 57 + "type": "github" 58 + } 59 + }, 60 + "root": { 61 + "inputs": { 62 + "flakelight": "flakelight", 63 + "flakelight-treefmt": "flakelight-treefmt", 64 + "nixpkgs": "nixpkgs" 65 + } 66 + }, 67 + "treefmt-nix": { 68 + "inputs": { 69 + "nixpkgs": [ 70 + "flakelight-treefmt", 71 + "flakelight", 72 + "nixpkgs" 73 + ] 74 + }, 75 + "locked": { 76 + "lastModified": 1770228511, 77 + "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=", 78 + "owner": "numtide", 79 + "repo": "treefmt-nix", 80 + "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7", 81 + "type": "github" 82 + }, 83 + "original": { 84 + "owner": "numtide", 85 + "repo": "treefmt-nix", 86 + "type": "github" 87 + } 88 + } 89 + }, 90 + "root": "root", 91 + "version": 7 92 + }
+32
flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + flakelight.url = "github:nix-community/flakelight"; 5 + flakelight-treefmt.url = "github:m15a/flakelight-treefmt"; 6 + 7 + flakelight.inputs.nixpkgs.follows = "nixpkgs"; 8 + flakelight-treefmt.inputs.flakelight.follows = "flakelight"; 9 + }; 10 + outputs = 11 + { flakelight, ... }@inputs: 12 + flakelight ./. { 13 + inherit inputs; 14 + 15 + imports = with inputs; [ 16 + flakelight-treefmt.flakelightModules.default 17 + ]; 18 + 19 + devShell.packages = 20 + pkgs: with pkgs; [ 21 + gleam 22 + erlang 23 + ]; 24 + 25 + treefmtConfig = 26 + { ... }: 27 + { 28 + programs.nixfmt.enable = true; 29 + programs.gleam.enable = true; 30 + }; 31 + }; 32 + }
+9
gleam.toml
··· 1 + name = "cycle" 2 + version = "1.0.0" 3 + 4 + description = "Put your function in a loop until the cycle (pun intended) breaks" 5 + licences = ["Unlicense"] 6 + repository = { type = "tangled", user = "fuzzko.neocities.org", repo = "cycle" } 7 + 8 + [dev-dependencies] 9 + gleeunit = ">= 1.9.0 and < 2.0.0"
+10
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "gleam_stdlib", version = "0.69.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "AAB0962BEBFAA67A2FBEE9EEE218B057756808DC9AF77430F5182C6115B3A315" }, 6 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 7 + ] 8 + 9 + [requirements] 10 + gleeunit = { version = ">= 1.9.0 and < 2.0.0" }
src/.gitkeep

This is a binary file and will not be displayed.

+80
src/cycle.gleam
··· 1 + //// Module to conveniently run function in loop 2 + //// 3 + //// Use-case for this are: 4 + //// - Maintain a state while sharing resources easily 5 + //// - Run operation forever 6 + //// - Limit cycles a loop can go through 7 + //// 8 + //// ```gleam 9 + //// cycle.start(with: #([], 0), run: fn(state) { 10 + //// let #(is, i) = state 11 + //// case i < 5 { 12 + //// True -> { 13 + //// let i = i + 1 14 + //// cycle.continue(#(list.prepend(is, i), i)) 15 + //// } 16 + //// _ -> cycle.stop(is) 17 + //// } 18 + //// }) 19 + //// ``` 20 + 21 + /// Message to the cycler if the cycle should continue or stop with a result. 22 + pub type Next(state, result) { 23 + /// Continue the cycle with new state. 24 + Continue(state) 25 + /// Stop the cycle with a result. 26 + Stop(result) 27 + } 28 + 29 + /// Convenient shortcut to `Continue`. 30 + pub fn continue(state: state) -> Next(state, result) { 31 + Continue(state) 32 + } 33 + 34 + /// Convenient shortcut to `Stop`. 35 + pub fn stop(result: result) -> Next(state, result) { 36 + Stop(result) 37 + } 38 + 39 + type LimitState(state) { 40 + LimitState(nth_cycle: Int, state: state) 41 + } 42 + 43 + /// Start a cycle with guard on max cycle, returns like normal `start`, otherwise `Error(Nil)` if n-th cycle has reached the max. 44 + pub fn safely_start( 45 + with state: state, 46 + max_cycle max: Int, 47 + run function: fn(state) -> Next(state, result), 48 + ) -> Result(result, Nil) { 49 + // no one except me and god can understand this piece of snippet 50 + // but now only god can understand whatever bullshit happens in here 51 + start(LimitState(0, state), fn(state) { 52 + case state.nth_cycle <= max { 53 + True -> 54 + case function(state.state) { 55 + Continue(inner_state) -> 56 + Continue(LimitState( 57 + state: inner_state, 58 + nth_cycle: state.nth_cycle + 1, 59 + )) 60 + Stop(result) -> stop(Ok(result)) 61 + } 62 + _ -> stop(Error(Nil)) 63 + } 64 + }) 65 + } 66 + 67 + /// Start a cycle, returns a result if function tell the cycler to stop. 68 + pub fn start( 69 + with state: state, 70 + run function: fn(state) -> Next(state, result), 71 + ) -> result { 72 + loop(state, function) 73 + } 74 + 75 + fn loop(state: state, function: fn(state) -> Next(state, result)) -> result { 76 + case function(state) { 77 + Continue(new_state) -> loop(new_state, function) 78 + Stop(result) -> result 79 + } 80 + }
test/.gitkeep

This is a binary file and will not be displayed.

+34
test/cycle_test.gleam
··· 1 + import cycle 2 + import gleeunit 3 + 4 + pub fn main() { 5 + gleeunit.main() 6 + } 7 + 8 + type CycleState { 9 + CycleState(xs: List(Int), i: Int) 10 + } 11 + 12 + pub fn cycle_test() { 13 + let j = 5 14 + let xs = 15 + cycle.start(with: CycleState(xs: [], i: 0), run: fn(state) { 16 + case state.i < j { 17 + True -> { 18 + let i = state.i + 1 19 + let xs = [i, ..state.xs] 20 + cycle.continue(CycleState(xs:, i:)) 21 + } 22 + _ -> cycle.stop(state.xs) 23 + } 24 + }) 25 + assert xs == [5, 4, 3, 2, 1] 26 + } 27 + 28 + pub fn max_cycle_test() { 29 + let result = 30 + cycle.safely_start(with: Nil, max_cycle: 1000, run: fn(_) { 31 + cycle.continue(Nil) 32 + }) 33 + assert result == Error(Nil) 34 + }