├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ └── test.yaml ├── .gitignore ├── .golangci.yml ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── assets ├── banner_dark.png └── banner_light.png ├── go.mod ├── internal ├── assert │ └── assert.go └── fakes │ ├── fakes.go │ └── reader.go └── it ├── chain.go ├── chain_test.go ├── channel.go ├── channel_test.go ├── compact.go ├── compact_test.go ├── cycle.go ├── cycle_test.go ├── drop.go ├── drop_test.go ├── enumerate.go ├── enumerate_test.go ├── exhausted.go ├── exhausted_test.go ├── filter.go ├── filter ├── filter.go └── filter_test.go ├── filter_test.go ├── filter_unique.go ├── filter_unique_test.go ├── integers.go ├── integers_test.go ├── iter.go ├── iter_test.go ├── itx ├── chain.go ├── chain_test.go ├── channel.go ├── channel_test.go ├── cycle.go ├── cycle_test.go ├── drop.go ├── drop_test.go ├── enumerate.go ├── enumerate_test.go ├── exhausted.go ├── exhausted_test.go ├── filter.go ├── filter_test.go ├── integers.go ├── integers_test.go ├── iter.go ├── iter_test.go ├── lines.go ├── lines_test.go ├── map.go ├── map_test.go ├── once.go ├── once_test.go ├── repeat.go ├── repeat_test.go ├── take.go ├── take_test.go ├── zip.go └── zip_test.go ├── lines.go ├── lines_test.go ├── map.go ├── map_test.go ├── once.go ├── once_test.go ├── op ├── op.go └── op_test.go ├── repeat.go ├── repeat_test.go ├── take.go ├── take_test.go ├── zip.go └── zip_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug 🐛]: ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What happened?** 11 | 12 | _Provide a description of the issue._ 13 | 14 | **What did you expect to happen?** 15 | 16 | _Explain what you would have expected to happen instead._ 17 | 18 | **Which version of go-functional were you using?** 19 | 20 | _Provide the version number (such as `v0.3.0`)._ 21 | 22 | **To Reproduce** 23 | 24 | _Provide a code snippet or steps to reproduce the issue._ 25 | 26 | **Do you intend to fix this issue yourself?** 27 | 28 | _Yes or no. This is so that the maintainers know to leave you time to make a 29 | contribution rather than just fixing it themselves._ 30 | 31 | **Additional context** 32 | 33 | _Add any other context about the problem here._ 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feature 🔨]: ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always 13 | frustrated when [...]. 14 | 15 | **Describe the solution you'd like** 16 | 17 | A clear and concise description of what you want to happen. 18 | 19 | **Provide code snippets to show how this new feature might be used.** 20 | 21 | Ideally, provide an [`Example`](https://go.dev/blog/examples) test that 22 | demonstrates the feature's usage. 23 | 24 | **Does this incur a breaking change?** 25 | 26 | Yes / no. 27 | 28 | **Do you intend to build this feature yourself?** 29 | 30 | Yes / no (so that it's clear that maintainers should allow you to make a 31 | contribution). 32 | 33 | **Additional context** 34 | Add any other context or screenshots about the feature request here. 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: { interval: weekly } 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Please provide a brief description of the change.** 2 | 3 | A sentence or two is fine, the rest should be clear from the code change and related issue. 4 | 5 | **Which issue does this change relate to?** 6 | 7 | Please provide a link to the issue that this change resolved. 8 | 9 | If there is no such issue, consider creating one first. Discussions concerning proposed changes ought to take place in an issue and not in pull requests. Pull requests not associated with an issue are less likely to be merged and more likely to ask for changes. 10 | 11 | **Contribution checklist.** 12 | 13 | _Replace the space in each box with "X" to check it off._ 14 | 15 | - [ ] I have read and understood the CONTRIBUTING guidelines 16 | - [ ] All commits in my PR conform to the commit hygiene section 17 | - [ ] I have added relevant tests 18 | - [ ] I have not added any dependencies 19 | 20 | **Additional context** 21 | 22 | _Add any other context about the problem here._ 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: { branches: [main] } 4 | pull_request: { branches: [main] } 5 | jobs: 6 | check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v5 10 | - uses: actions/setup-go@v6 11 | with: 12 | go-version: "1.25" 13 | cache: false 14 | - run: make check 15 | env: { SKIP_LINT: true } 16 | - uses: golangci/golangci-lint-action@v8 17 | with: { version: "latest" } 18 | - run: make cov 19 | - uses: codecov/codecov-action@v5 20 | env: { CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" } 21 | test: 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, windows-latest, macos-latest] 25 | go-version: ["1.23", "1.24", "1.25"] 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - uses: actions/checkout@v5 29 | - uses: actions/setup-go@v6 30 | with: 31 | go-version: ${{ matrix.go-version }} 32 | cache: false 33 | - run: make test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.txt 2 | .vscode/ 3 | go.work 4 | .envrc -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - asasalint 6 | - asciicheck 7 | - bidichk 8 | - decorder 9 | - depguard 10 | - durationcheck 11 | - errcheck 12 | - errchkjson 13 | - errorlint 14 | - exhaustive 15 | - gochecknoglobals 16 | - gocritic 17 | - gocyclo 18 | - gosec 19 | - govet 20 | - ineffassign 21 | - misspell 22 | - nilerr 23 | - staticcheck 24 | - unused 25 | settings: 26 | cyclop: 27 | max-complexity: 15 28 | package-average: 0.5 29 | depguard: 30 | rules: 31 | main: 32 | allow: 33 | - $gostd 34 | - github.com/BooleanCat/go-functional/v2 35 | govet: 36 | enable-all: true 37 | misspell: 38 | locale: US 39 | exclusions: 40 | generated: lax 41 | presets: 42 | - comments 43 | - common-false-positives 44 | - legacy 45 | - std-error-handling 46 | paths: 47 | - third_party$ 48 | - builtin$ 49 | - examples$ 50 | formatters: 51 | enable: 52 | - gofmt 53 | exclusions: 54 | generated: lax 55 | paths: 56 | - third_party$ 57 | - builtin$ 58 | - examples$ 59 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "proseWrap": "always", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a 6 | harassment-free experience for everyone, regardless of age, body size, visible or invisible 7 | disability, ethnicity, sex characteristics, gender identity and expression, level of experience, 8 | education, socio-economic status, nationality, personal appearance, race, religion, or sexual 9 | identity and orientation. 10 | 11 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and 12 | healthy community. 13 | 14 | ## Our Standards 15 | 16 | Examples of behaviour that contributes to a positive environment for our community include: 17 | 18 | - Demonstrating empathy and kindness toward other people 19 | - Being respectful of differing opinions, viewpoints, and experiences 20 | - Giving and gracefully accepting constructive feedback 21 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the 22 | experience 23 | - Focusing on what is best not just for us as individuals, but for the overall community 24 | 25 | Examples of unacceptable behaviour include: 26 | 27 | - The use of sexualized language or imagery, and sexual attention or advances of any kind 28 | - Trolling, insulting or derogatory comments, and personal or political attacks 29 | - Public or private harassment 30 | - Publishing others' private information, such as a physical or email address, without their 31 | explicit permission 32 | - Other conduct which could reasonably be considered inappropriate in a professional setting 33 | 34 | ## Enforcement Responsibilities 35 | 36 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behaviour 37 | and will take appropriate and fair corrective action in response to any behaviour that they deem 38 | inappropriate, threatening, offensive, or harmful. 39 | 40 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, 41 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and 42 | will communicate reasons for moderation decisions when appropriate. 43 | 44 | ## Scope 45 | 46 | This Code of Conduct applies within all community spaces, and also applies when an individual is 47 | officially representing the community in public spaces. Examples of representing our community 48 | include using an official e-mail address, posting via an official social media account, or acting as 49 | an appointed representative at an online or offline event. 50 | 51 | ## Enforcement 52 | 53 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to the 54 | community leaders responsible for enforcement at tomgodkin@pm.me. All complaints will be reviewed 55 | and investigated promptly and fairly. 56 | 57 | All community leaders are obligated to respect the privacy and security of the reporter of any 58 | incident. 59 | 60 | ## Enforcement Guidelines 61 | 62 | Community leaders will follow these Community Impact Guidelines in determining the consequences for 63 | any action they deem in violation of this Code of Conduct: 64 | 65 | ### 1. Correction 66 | 67 | **Community Impact**: Use of inappropriate language or other behaviour deemed unprofessional or 68 | unwelcome in the community. 69 | 70 | **Consequence**: A private, written warning from community leaders, providing clarity around the 71 | nature of the violation and an explanation of why the behaviour was inappropriate. A public apology 72 | may be requested. 73 | 74 | ### 2. Warning 75 | 76 | **Community Impact**: A violation through a single incident or series of actions. 77 | 78 | **Consequence**: A warning with consequences for continued behaviour. No interaction with the people 79 | involved, including unsolicited interaction with those enforcing the Code of Conduct, for a 80 | specified period of time. This includes avoiding interactions in community spaces as well as 81 | external channels like social media. Violating these terms may lead to a temporary or permanent ban. 82 | 83 | ### 3. Temporary Ban 84 | 85 | **Community Impact**: A serious violation of community standards, including sustained inappropriate 86 | behaviour. 87 | 88 | **Consequence**: A temporary ban from any sort of interaction or public communication with the 89 | community for a specified period of time. No public or private interaction with the people involved, 90 | including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this 91 | period. Violating these terms may lead to a permanent ban. 92 | 93 | ### 4. Permanent Ban 94 | 95 | **Community Impact**: Demonstrating a pattern of violation of community standards, including 96 | sustained inappropriate behaviour, harassment of an individual, or aggression toward or 97 | disparagement of classes of individuals. 98 | 99 | **Consequence**: A permanent ban from any sort of public interaction within the community. 100 | 101 | ## Attribution 102 | 103 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at 104 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 105 | 106 | Community Impact Guidelines were inspired by 107 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 108 | 109 | [homepage]: https://www.contributor-covenant.org 110 | 111 | For answers to common questions about this code of conduct, see the FAQ at 112 | https://www.contributor-covenant.org/faq. Translations are available at 113 | https://www.contributor-covenant.org/translations. 114 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to go-functional 2 | 3 | Contributions to this project are very welcome! This guide should help with instructions on how to 4 | submit changes. Contributions can be made in the form of GitHub 5 | [issues](https://github.com/BooleanCat/go-functional/issues) or 6 | [pull requests](https://github.com/BooleanCat/go-functional/pulls). 7 | 8 | When submitting an issue, please choose the relevant template or choose a blank issue if your query 9 | doesn't naturally fit into an existing template. The pull request template contains a contribution 10 | checklist. 11 | 12 | ## Zero-dependency 13 | 14 | This project is a zero-dependency project - which means that consumers using this project's packages 15 | must only incur one dependency: go-functional. 16 | 17 | Development dependencies are OK as they will not be included as dependencies to end-users (such as 18 | `golangci-lint`). 19 | 20 | ## Development dependencies 21 | 22 | 1. [golangci-lint](https://github.com/golangci/golangci-lint) is used to lint the project when 23 | running `make check`. 24 | 2. [counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) is used to generate new fakes 25 | (using `go generate ./...`). Fakes are declared in `internal/fakes/fakes.go`. 26 | 27 | ## Commit hygiene 28 | 29 | - Commits should contain only a single change 30 | - Commit messages must use imperative language (e.g. `Add iter.Fold collection function`) 31 | - Commit messages must explain what is changed, not how it is changed 32 | - The first line of a commit message should be a terse description of the change containing 72 33 | characters or fewer 34 | 35 | ## Running tests 36 | 37 | Run tests with `make test` from the project root directory. 38 | 39 | Tests are written using Go's `testing` package and helpers are available in `internal/assert`. 40 | 41 | Code is linted using `golangci-lint`. The linter may be run using `make lint`. 42 | 43 | ## Different types of changes 44 | 45 | ### Bug fixes 46 | 47 | Bug reports are appreciated ahead of bug fixes as early reporting allows the community to be aware 48 | of any issues ahead of a fix being submitted. If you intend to fix a bug after reporting, that is 49 | greatly appreciated - just make sure to mention you intend to work on it on the issue report so the 50 | maintainers are aware and leave you the chance to make a contribution. 51 | 52 | When submitting a bug fix PR, a test must be added (or an existing test modified) that exposes the 53 | bug and your change must make that test pass. 54 | 55 | ### New features 56 | 57 | Issues should be opened ahead of submitting a PR to added a new feature. This is to prevent you 58 | wasting your time should a feature not be desirable and allows others to have input into the 59 | conversation. 60 | 61 | All new functionality must be fully tested and new public functions must include an 62 | [`Example` test](https://go.dev/blog/examples) that will be used by the reference docs to 63 | demonstrate its use. 64 | 65 | Mark pull requests as "Draft" if you intend to use the pull request as a workspace but are not yet 66 | ready to receive unsolicited feedback on specifics like commit messages or failing tests. 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tom Godkin 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .phony: test check lint 2 | 3 | GO_BINARY ?= go 4 | 5 | check: lint 6 | @$(GO_BINARY) vet ./... 7 | @gofmt -l . 8 | @test -z "$$( gofmt -l . )" 9 | 10 | lint: 11 | ifndef SKIP_LINT 12 | @golangci-lint run ./... 13 | endif 14 | 15 | test: 16 | $(GO_BINARY) test -race -v ./... 17 | 18 | cov: SHELL:=/bin/bash 19 | cov: 20 | $(GO_BINARY) test -race -coverprofile=coverage.txt -covermode=atomic $$( $(GO_BINARY) list ./... | grep -v assert | grep -v fakes ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional Programming in Go 2 | 3 | [![GitHub release (with filter)](https://img.shields.io/github/v/release/BooleanCat/go-functional?sort=semver&logo=Go&color=%23007D9C&include_prereleases)](https://github.com/BooleanCat/go-functional/releases) 4 | [![Actions Status](https://github.com/BooleanCat/go-functional/workflows/test/badge.svg)](https://github.com/BooleanCat/go-functional/actions) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/BooleanCat/go-functional/v2.svg)](https://pkg.go.dev/github.com/BooleanCat/go-functional/v2) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/BooleanCat/go-functional/v2)](https://goreportcard.com/report/github.com/BooleanCat/go-functional/v2) 7 | [![codecov](https://codecov.io/gh/BooleanCat/go-functional/branch/main/graph/badge.svg?token=N2E43RSR14)](https://codecov.io/gh/BooleanCat/go-functional) 8 | 9 | A library of iterators for use with [iter.Seq](https://pkg.go.dev/iter#Seq). Requires Go 1.23+. 10 | 11 | ```go 12 | // The first 5 natural numbers 13 | numbers := slices.Collect( 14 | it.Take(it.NaturalNumbers[int](), 5), 15 | ) 16 | 17 | // All even numbers 18 | evens := it.Filter(it.NaturalNumbers[int](), filter.IsEven) 19 | 20 | // String representations of integers 21 | numbers := it.Map(it.NaturalNumbers[int](), strconv.Itoa) 22 | ``` 23 | 24 | _[Reference documentation](https://pkg.go.dev/github.com/BooleanCat/go-functional/v2)_ 25 | 26 | 27 | 28 | 29 | go-functional gopher banner image 30 | 31 | 32 | ## Installation 33 | 34 | ```terminal 35 | go get github.com/BooleanCat/go-functional/v2@latest 36 | ``` 37 | 38 | ## Overview 39 | 40 | Most functions offered by this package are either consumers or iterators. 41 | 42 | [Consumers](#consumers) will iterate over an iterator and completely or partially drain them of 43 | values and (in most cases) collect the values into a data type. 44 | 45 | [Iterators](#iterators) are functions that yield new values and can be ranged over. See Go's 46 | documentation for iterators for more details. 47 | 48 | ## Consumers 49 | 50 | The standard libary provides functions to collect iterators in the 51 | [slices](https://pkg.go.dev/slices) and [maps](https://pkg.go.dev/maps) packages that should satisfy 52 | most cases where collection is needed. 53 | 54 | This package provides additional collection methods and makes existing consumers from the standard 55 | library chainable. 56 | 57 | 58 | > [!WARNING] 59 | > Attempting to collect infinite iterators will cause an infinite loop and likely deadlock. Consider 60 | > bounding infinite iterators before collect (for example using [Take](#take)). 61 | 62 | ### All & Any 63 | 64 | `All` will return `true` if all values yielded by an iterator are `true`, or `false` otherwise. 65 | Iteration will terminate early if `false` is encountered. Empty iterators will return `true`. 66 | 67 | ```go 68 | it.All(slices.Values([]bool{true, false, true})) // false 69 | ``` 70 | 71 | `Any` will return `true` if any value yielded by an iterator is `true`, or `false` otherwise. 72 | Iteration will terminate early if `true` is encountered. Empty iterators will return `false`. 73 | 74 | ```go 75 | it.Any(slices.Values([]bool{false, false, true})) // true 76 | ``` 77 | 78 | 79 | > [!NOTE] 80 | > The `itx` package does not contain `All` or `Any` due to limitations with Go's type system. 81 | 82 | ### Collect 83 | 84 | In most cases [slices.Collect](https://pkg.go.dev/slices#Collect) from the standard library may be 85 | used to collect items from an iterator into a slice. There are several other variants of collect 86 | available for use for different use cases. 87 | 88 | ```go 89 | // Chainable 90 | numbers := itx.NaturalNumbers[int]().Take(5).Collect() 91 | 92 | // Collect an iter.Seq2[V, W] into two slices 93 | keys, values := it.Collect2(maps.All(map[string]int{"one": 1, "two": 2})) 94 | 95 | // As above, but chainable 96 | keys, values := itx.FromMap(map[string]int{"one": 1, "two": 2}).Collect() 97 | ``` 98 | 99 |

TryCollect & MustCollect

100 | 101 | Dealing with iterators that return `T, error` can involve the boilerplate of checking that the 102 | returned slice of errors only contains `nil`. `TryCollect` solves this by collecting all values into 103 | a slice and returning a single error: the first one encountered. 104 | 105 | ```go 106 | text := strings.NewReader("one\ntwo\nthree\n") 107 | 108 | if lines, err := it.TryCollect(it.LinesString(text)); err != nil { 109 | fmt.Println(lines) 110 | } 111 | ``` 112 | 113 | `MustCollect` is similar except that if an error is encountered then a panic will occur. 114 | 115 | ```go 116 | text := strings.NewReader("one\ntwo\nthree\n") 117 | 118 | lines := it.MustCollect(it.LinesString(text)) 119 | ``` 120 | 121 | 122 | > [!TIP] 123 | > Use `MustCollect` when you can guarantee that no error will occur (such as with 124 | > [strings.Reader](https://pkg.go.dev/strings#Reader)). 125 | 126 | 127 | > [!NOTE] 128 | > If an error is encountered, collection stops. This means the iterator being collected may not be 129 | > fully drained. 130 | 131 | 132 | > [!NOTE] 133 | > The `itx` package does not contain `TryCollect` or `MustCollect` due to limitations with Go's type 134 | > system. 135 | 136 | ### ForEach 137 | 138 | ForEach consumes an iterator and applies a function to each value yielded. 139 | 140 | ```go 141 | it.ForEach(slices.Values([]int{1, 2, 3}), func(number int) { 142 | fmt.Println(number) 143 | }) 144 | 145 | // Chainable 146 | itx.FromSlice([]int{1, 2, 3}).ForEach(func(number int) { 147 | fmt.Println(number) 148 | }) 149 | 150 | // For each member of an iter.Seq2 151 | it.ForEach2(slices.All([]int{1, 2, 3}), func(index int, number int) { 152 | fmt.Println(index, number) 153 | }) 154 | 155 | // As above, but chainable 156 | itx.FromSlice([]int{1, 2, 3}).Enumerate().ForEach(func(index int, number int) { 157 | fmt.Println(index, number) 158 | }) 159 | ``` 160 | 161 | ### Fold 162 | 163 | Fold every element into an accumulator by applying a function and passing an initial value. 164 | 165 | ```go 166 | it.Fold(slices.Values([]int{1, 2, 3}), op.Add, 0) 167 | 168 | // Fold an iter.Seq2 169 | it.Fold2(slices.All([]int{1, 2, 3}), func(i, a, b int) int { 170 | return i + 1 171 | }, 0) 172 | ``` 173 | 174 | 175 | > [!TIP] 176 | > The [op package](it/op/op.go) contains some simple, pre-defined operation functions. 177 | 178 | 179 | > [!NOTE] 180 | > The `itx` package does not contain `Fold` due to limitations with Go's type system. 181 | 182 | ### Max & Min 183 | 184 | `Max` and `Min` consume an iterator and return the maximum or minimum value yielded and true if the 185 | iterator contained at least one value, or the zero value and false if the iterator was empty. 186 | 187 | The type of the value yielded by the iterator must be `comparable`. 188 | 189 | ```go 190 | max, ok := it.Max(slices.Values([]int{1, 2, 3})) 191 | min, ok := it.Min(slices.Values([]int{1, 2, 3})) 192 | ``` 193 | 194 | 195 | > [!NOTE] 196 | > The `itx` package does not contain `Max` or `Min` due to limitations with Go's type system. 197 | 198 | ### Len 199 | 200 | Len consumes an iterator and returns the number of values yielded. 201 | 202 | ```go 203 | it.Len(slices.Values([]int{1, 2, 3})) 204 | 205 | // Chainable 206 | itx.FromSlice([]int{1, 2, 3}).Len() 207 | 208 | // Len of an iter.Seq2 209 | it.Len2(slices.All([]int{1, 2, 3})) 210 | 211 | // As above, but chainable 212 | itx.FromSlice([]int{1, 2, 3}).Enumerate().Len() 213 | ``` 214 | 215 | ### Find 216 | 217 | Find consumes an iterator until a value is found that satisfies a predicate. It returns the value 218 | and true if one was found, or the zero value and false if the iterator was exhausted before a value 219 | was found. 220 | 221 | ```go 222 | found, ok := it.Find(slices.Values([]int{1, 2, 3}), func(i int) bool { 223 | return i == 2 224 | }) 225 | 226 | // Chainable 227 | value, ok := itx.FromSlice([]int{1, 2, 3}).Find(func(number int) bool { 228 | return number == 2 229 | }) 230 | 231 | // Finding within an iter.Seq2 232 | index, value, ok := it.Find2(slices.All([]int{1, 2, 3}), func(i, v int) bool { 233 | return i == 2 234 | }) 235 | 236 | // As above, but chainable 237 | index, value, ok := itx.FromSlice([]int{1, 2, 3}).Enumerate().Find(func(index int, number int) bool { 238 | return index == 1 239 | }) 240 | ``` 241 | 242 | 243 | > [!TIP] 244 | > The [filter package](it/filter/filter.go) contains some simple, pre-defined predicate functions. 245 | 246 | ### Contains 247 | 248 | Contains consumes an iterator until the provided value is found, returning true if the value was 249 | found or false if the iterator was exhausted. 250 | 251 | ```go 252 | ok := it.Contains(slices.Values([]int{1, 2, 3}), 2) 253 | ``` 254 | 255 | 256 | > [!NOTE] 257 | > The `itx` package does not contain `Contains` due to limitations with Go's type system. 258 | 259 | ### From, FromSlice, FromMap & Seq 260 | 261 | The itx package contains some helper functions to convert iterators, slices or maps directly into 262 | chainable iterators to avoid some boilerplate. 263 | 264 | ```go 265 | itx.From(slices.Values([]int{1, 2, 3})).Collect() 266 | 267 | itx.From2(maps.All(map[int]string{1: "one"})) 268 | 269 | itx.FromSlice([]int{1, 2, 3}).Collect() 270 | 271 | itx.FromMap(map[int]int{1: 2}).Collect() 272 | ``` 273 | 274 | The `itx` package also contains a helper function Seq that will convert a chainable iterator into an 275 | [iter.Seq](https://pkg.go.dev/iter#Seq) so that it can be used in functions that accept that type 276 | (such as the standard library). 277 | 278 | The standard library functions that work with iterators (such as 279 | [slices.Collect](https://pkg.go.dev/slices#Collect)) accept the 280 | [iter.Seq](https://pkg.go.dev/iter#Seq) family of types. This precludes those functions from 281 | accepting types with another alias (such as `itx.Iterator`) with the same type definition. This 282 | means it is necessary to "covert" an `itx.Iterator` into an [iter.Seq](https://pkg.go.dev/iter#Seq) 283 | before passing the iterator into those functions. 284 | 285 | ```go 286 | slices.Collect(itx.NaturalNumbers[int]().Take(3).Seq()) 287 | ``` 288 | 289 | 290 | > [!TIP] 291 | > go-functional's functions that accept iterators always accept `func(func(V) bool)` or 292 | > `func(func(V, W) bool)` rather than any specific type alias so that they can accept any type alias 293 | > with the definitions [iter.Seq](https://pkg.go.dev/iter#Seq), 294 | > [iter.Seq2](https://pkg.go.dev/iter#Seq2), `itx.Iterator`, `itx.Iterator2` or any other 295 | > third-party types aliased to the same type. 296 | 297 | ### ToChannel 298 | 299 | ToChannel sends yielded values to a channel. 300 | 301 | The channel is closed when the iterator is exhausted. Beware of leaked go routines when using this 302 | function with an infinite iterator. 303 | 304 | ```go 305 | channel := it.ToChannel(slices.Values([]int{1, 2, 3})) 306 | 307 | for number := range channel { 308 | fmt.Println(number) 309 | } 310 | 311 | // Chainable 312 | channel := itx.FromSlice([]int{1, 2, 3}).ToChannel() 313 | 314 | for number := range channel { 315 | fmt.Println(number) 316 | } 317 | ``` 318 | 319 | 320 | > [!NOTE] 321 | > Unlike most consumers, the iterator is not immediately consumed by ToChannel. Instead is it 322 | > consumed as values are pulled from the channel. 323 | 324 | ### Drain 325 | 326 | Drain consumes an iterator's values and drops them. 327 | 328 | ```go 329 | printValue := func(n int) int { 330 | fmt.Println(n) 331 | return n 332 | } 333 | 334 | it.Drain(it.Map(slices.Values([]int{1, 2, 3}), printValue)) 335 | 336 | // Chainable 337 | itx.From(it.Map(slices.Values([]int{1, 2, 3}), printValue)).Drain() 338 | 339 | // Drain an iter.Seq2 340 | printValue2 := func(i, n int) (int, int) { 341 | fmt.Println(n) 342 | return i, n 343 | } 344 | 345 | it.Drain2(it.Map2(slices.All([]int{1, 2, 3}), printValue2)) 346 | 347 | // As above, but chainable 348 | itx.From2(it.Map2(slices.All([]int{1, 2, 3}), printValue2)).Drain() 349 | ``` 350 | 351 | 352 | > [!TIP] 353 | > Use `Drain` to consume an iterator to invoke any side effects when you don't need to collect the 354 | > values. 355 | 356 | ## Iterators 357 | 358 | This library contains two kinds of iterators in the `it` and `itx` packages. In most cases you'll 359 | find the same iterators in each package, the difference between them being that the iterators in the 360 | `itx` package can be "dot-chained" (e.g. `iter.Filter(...).Take(3).Collect()`) and those in `it` 361 | cannot. 362 | 363 | Iterators within the `it` package are of the type [iter.Seq](https://pkg.go.dev/iter#Seq) or 364 | [iter.Seq2](https://pkg.go.dev/iter#Seq2) (from the standard library). Iterators within the `itx` 365 | package are of the type `itx.Iterator[V]` or `itx.Iterator2[V, W]`. 366 | 367 | There are two important factors to consider when using iterators: 368 | 369 | 1. Some iterators yield an infinite number of values and care should be taken to avoid consuming 370 | (using functions such as [slices.Collect](https://pkg.go.dev/slices#Collect)) otherwise it's 371 | likely to cause an infinite while loop. 372 | 2. Many iterators take another iterator as an argument (such as [Filter](#filter) or Map). Avoid 373 | using an iterator after it has been passed to another iterator otherwise you'll risk multiple 374 | functions consuming a single (likely not thread-safe) iterator and causing confusing and 375 | difficult to debug behaviour. 376 | 377 | ### Chain 378 | 379 | Chain yields values from multiple iterators in the sequence they are provided in. Think of it as 380 | glueing many iterators together. 381 | 382 | When provided zero iterators it will behave like [Exhausted](#exhausted). 383 | 384 | ```go 385 | numbers := it.Chain(slices.Values([]int{1, 2}), slices.Values([]int{3, 4})) 386 | 387 | pairs := itx.FromSlice([]int{1, 2}).Chain(slices.Values([]int{3, 4})) 388 | 389 | pairs := it.Chain2(maps.All(map[string]int{"a": 1}), maps.All(map[string]int{"b": 2})) 390 | 391 | pairs := itx.FromMap(map[string]int{"a": 1}).Chain(maps.All(map[string]int{"b": 2})) 392 | ``` 393 | 394 | ### FromChannel 395 | 396 | FromChannel pulls values from a channel and yields them via an iterator. The usual concerns around 397 | channel deadlocks apply here. 398 | 399 | The iterator is exhausted when the channel is closed and it is the responsibility of the caller to 400 | close the channel. 401 | 402 | ```go 403 | items := make(chan int) 404 | 405 | go func() { 406 | defer close(items) 407 | items <- 1 408 | items <- 2 409 | }() 410 | 411 | for number := range it.FromChannel(items) { 412 | fmt.Println(number) 413 | } 414 | 415 | // Chainable 416 | items := make(chan int) 417 | 418 | go func() { 419 | defer close(items) 420 | items <- 1 421 | items <- 0 422 | }() 423 | 424 | for number := range itx.FromChannel(items).Exclude(filter.IsZero) { 425 | fmt.Println(number) 426 | } 427 | ``` 428 | 429 | 430 | > [!WARNING] 431 | > In order to prevent a deadlock, the channel must be closed before attemping to stop the iterator 432 | > when it's used in a pull style. See [iter.Pull](https://pkg.go.dev/iter#Pull). 433 | 434 | ### Compact 435 | 436 | Compact yields all values from a delegate iterator that are not zero values. It is functionally 437 | equivalent to `it.Exclude(delegate, filter.IsZero)`. 438 | 439 | ```go 440 | words := it.Compact(slices.Values([]string{"foo", "", "bar", "", ""})) 441 | ``` 442 | 443 | 444 | > [!NOTE] 445 | > The `itx` package does not contain `Compact` due to limitations with Go's type system. 446 | 447 | ### Cycle 448 | 449 | Cycle yields all values from an iterator before returning to the beginning and yielding all values 450 | again (indefinitely). 451 | 452 | ```go 453 | numbers := it.Take(it.Cycle(slices.Values([]int{1, 2})), 5) 454 | 455 | // Chainable 456 | numbers := itx.FromSlice([]int{1, 2}).Cycle().Take(5) 457 | 458 | // Cycling an iter.Seq2 459 | numbers := it.Take2(it.Cycle2(maps.All(map[int]string{1: "one"})), 5) 460 | 461 | // As above, but chainable 462 | numbers := itx.FromMap(map[int]string{1: "one"}).Cycle().Take(5) 463 | ``` 464 | 465 | 466 | > [!NOTE] 467 | > Since cycle needs to store all values yielded in memory, its memory usage will grow as the first 468 | > cycle is consumed and remain at a constant size on subsequent cycles. 469 | 470 | 471 | > [!WARNING] 472 | > This iterator yields an infinite number of values and care should be taken when consuming it 473 | > otherwise it's likely to result in an infinite while loop. Consider bounding the size of the 474 | > iterator before consuming (e.g. using [Take](#take)). 475 | 476 |

Drop & DropWhile

477 | 478 | Drop yields all values from a delegate iterator after dropping a number of values from the 479 | beginning. Values are not dropped immediately, but when consumption begins. 480 | 481 | When dropping a number of values larger than the length of the iterator, it behaves like 482 | [Exhausted](#exhausted). 483 | 484 | ```go 485 | numbers := it.Drop(slices.Values([]int{1, 2, 3, 4, 5}), 2) 486 | 487 | // Chainable 488 | numbers := itx.FromSlice([]int{1, 2, 3, 4, 5}).Drop(2) 489 | 490 | // Dropping on iter.Seq2 491 | numbers := it.Drop2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 1) 492 | 493 | // As above, but chainable 494 | numbers := itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Drop(1) 495 | ``` 496 | 497 | DropWhile drops values from the provided iterator whilst the predicate returns true for each value. 498 | After the first value results in the predicate returning false, the iterator resumes as normal. 499 | 500 | ```go 501 | slices.Collect(it.DropWhile(slices.Values([]int{1, 2, 3, 4, 5}), filter.LessThan(3))) 502 | 503 | // Chainable 504 | itx.FromSlice([]int{1, 2, 3, 4, 5}).DropWhile(filter.LessThan(3)).Collect() 505 | 506 | lessThanThree := func(string, number int) { return number < 3 } 507 | 508 | // Taking from iter.Seq2 509 | maps.Collect(it.DropWhile2(maps.All(map[string]int{"one": 1, "four": 4}), lessThanThree)) 510 | 511 | // As above, but chainable 512 | itx.FromMap(map[string]int{"one": 1, "four": 4}).DropWhile(lessThanThree).Collect() 513 | ``` 514 | 515 | ### Enumerate 516 | 517 | Enumerating an [iter.Seq](https://pkg.go.dev/iter#Seq) like iterator returns an 518 | [iter.Seq2](https://pkg.go.dev/iter#Seq2) like iterator yielding the index of each value and the 519 | value. 520 | 521 | ```go 522 | indexedValues := it.Enumerate(slices.Values([]int{1, 2, 3})) 523 | 524 | // Chainable 525 | indexedValues := itx.FromSlice([]int{1, 2, 3}).Enumerate() 526 | ``` 527 | 528 | 529 | > [!TIP] 530 | > When iterating over a slice and immediately enumerating, consider instead using the standard 531 | > library's [slices.All](https://pkg.go.dev/slices#All) function rather than this. 532 | 533 | ### Exhausted 534 | 535 | Exhausted is an iterator that yields no values. 536 | 537 | ```go 538 | slices.Collect(it.Exhausted[int]()) 539 | 540 | // Chainable 541 | it.Exhausted[int]().Collect() 542 | 543 | // Exhausted iter.Seq2 544 | maps.Collect(it.Exhausted2[int, string]()) 545 | 546 | // As above, but chainable 547 | itx.Exhausted2[int, string]().Collect() 548 | ``` 549 | 550 |

Filter & Exclude

551 | 552 | Filter yields values from an iterator that satisfy a predicate. Likewise, exclude yields values from 553 | an iterator that do not satisfy a predicate. 554 | 555 | ```go 556 | it.Filter(slices.Values([]int{1, 2, 3, 4, 5}), filter.IsEven) 557 | it.Exclude(slices.Values([]int{1, 2, 3, 4, 5}), filter.IsEven) 558 | 559 | // Chainable 560 | itx.FromSlice([]int{1, 2, 3, 4, 5}).Filter(filter.IsEven) 561 | itx.FromSlice([]int{1, 2, 3, 4, 5}).Exclude(filter.IsEven) 562 | 563 | // Filtering an iter.Seq2 564 | isOne := func(n int, _ string) bool { return n == 1 } 565 | 566 | it.Filter2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), isOne) 567 | it.Exclude2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), isOne) 568 | 569 | // As above, but chainable 570 | itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Filter(isOne) 571 | itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Exclude(isOne) 572 | ``` 573 | 574 | 575 | > [!TIP] 576 | > The [filter package](it/filter/filter.go) contains some simple, pre-defined predicate functions. 577 | 578 | ### FilterError & ExcludeError 579 | 580 | Similar to [Filter and Exclude](#filter), these functions filter values from an iterator using a 581 | predicate that may return an error. 582 | 583 | ```go 584 | isFoo := func(s string) (bool, error) { return s == "foo", nil } 585 | 586 | values, err := it.TryCollect(it.FilterError(slices.Values([]string{"foo", "bar"}), isFoo)) 587 | values, err := it.TryCollect(it.ExcludeError(slices.Values([]string{"foo", "bar"}), isFoo)) 588 | 589 | // Chainable 590 | values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).FilterError(isFoo)) 591 | values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).ExcludeError(isFoo)) 592 | ``` 593 | 594 | ### FilterUnique 595 | 596 | FilterUnique yields only the unique values from an iterator. 597 | 598 | ```go 599 | values := it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4})) 600 | ``` 601 | 602 | 603 | > [!WARNING] 604 | > Unique values are stored in memory until the iterator is exhausted. Large iterators with 605 | > many unique values may use a large amount of memory. 606 | 607 | 608 | > [!NOTE] 609 | > The `itx` package does not contain `FilterUnique` due to limitations with Go's type system. 610 | 611 | ### Integers 612 | 613 | Integers yields all integers in the range [start, stop) with the given step. 614 | 615 | ```go 616 | for i := range it.Integers[uint](0, 3, 1) { 617 | fmt.Println(i) 618 | } 619 | 620 | // Chainable 621 | for i := range itx.Integers[uint](0, 3, 1).Drop(1) { 622 | fmt.Println(i) 623 | } 624 | ``` 625 | 626 | ### Lines & LinesString 627 | 628 | Lines yields lines from an [io.Reader](https://pkg.go.dev/io#Reader). Lines are split using the 629 | standard library's [bufio.Scanner](https://pkg.go.dev/bufio#Scanner). Each value yielded from the 630 | iterator is a line from the provided reader. Empty lines will result in empty values. 631 | 632 | Since reading from an [io.Reader](https://pkg.go.dev/io#Reader) can fail, each line is returned with 633 | a corresponding `error` value. 634 | 635 | LinesString behaves exactly like Lines except that its values are strings rather than byte slices. 636 | 637 | ```go 638 | buffer := strings.NewReader("one\ntwo\nthree\n") 639 | 640 | for line, err := range it.Lines(buffer) { 641 | if err != nil { 642 | fmt.Println(err) 643 | break 644 | } 645 | 646 | fmt.Println(string(line)) 647 | } 648 | 649 | for line, err := range it.LinesString(buffer) { 650 | if err != nil { 651 | fmt.Println(err) 652 | break 653 | } 654 | 655 | fmt.Println(line) 656 | } 657 | ``` 658 | 659 | 660 | > [!TIP] 661 | > Consider using [TryCollect](#trycollect) in conjunction with Lines to make error collection less 662 | > cumbersome: 663 | > 664 | > ```go 665 | > lines, err := it.TryCollect(it.LinesString(buffer)) 666 | > ``` 667 | 668 | 669 | > [!TIP] 670 | > For cases where errors will never result from reading, such as with 671 | > [bytes.Buffer](https://pkg.go.dev/bytes#Buffer), consider dropping the error value before 672 | > collection: 673 | > 674 | > ```go 675 | > lines := itx.LinesString(buffer).Left().Collect() 676 | > ``` 677 | 678 | 679 | > [!WARNING] 680 | > As with [bufio.Scanner](https://pkg.go.dev/bufio#Scanner), there is a maximum line length per line 681 | > of 65536 characters. 682 | 683 |

Map & Transform

707 | > [!NOTE] 708 | > The `itx` package does not contain `Map` due to limitations with Go's type system. Instead a 709 | > limited from of `Map` called `Transform` is provided where the type returned from operations is 710 | > the same as a type of the iterator's values. 711 | > 712 | > A chainable Map will be added should Go's type system ever support new generic type parameters on 713 | > methods. 714 | 715 | 716 | > [!TIP] 717 | > If you wish to chain operations on `Map`, you can do so by first converting it to an 718 | > `itx.Iterator` like so: 719 | > 720 | > ```go 721 | > itx.From(it.Map(slices.Values([]int{1, 2, 3}), double)).Collect() 722 | > ``` 723 | 724 | ### MapError & TransformError 725 | 726 | `MapError` and `TransformError` behave like [Map and Transform](#map) except that they accept a 727 | function that may return an error. 728 | 729 | ```go 730 | double := func(n int, err error) int { return n * 2, nil } 731 | 732 | it.MapError(slices.Values([]int{1, 2, 3}), double) 733 | 734 | // Limited chainable flavour of MapError 735 | itx.FromSlice([]int{1, 2, 3}).TransformError(double) 736 | ``` 737 | 738 | 739 | > [!NOTE] 740 | > The `itx` package does not contain `MapError` due to limitations with Go's type system. Instead a 741 | > limited from of `MapError` called `TransformError` is provided where the type returned from 742 | > operations is the same as a type of the iterator's values. 743 | > 744 | > A chainable MapError will be added should Go's type system ever support new generic type 745 | > parameters on methods. 746 | 747 | 748 | > [!TIP] 749 | > If you wish to chain operations on `MapError`, you can do so by first converting it to an 750 | > `itx.Iterator2` like so: 751 | > 752 | > ```go 753 | > itx.From2(it.MapError(slices.Values([]int{1, 2, 3}), double)).Collect() 754 | > ``` 755 | 756 | ### NaturalNumbers 757 | 758 | NaturalNumbers yields all non-negative integers in ascending order. 759 | 760 | ```go 761 | for i := range it.Take(it.NaturalNumbers[int](), 3) { 762 | fmt.Println(i) 763 | } 764 | 765 | // Chainable 766 | for i := range itx.NaturalNumbers[int]().Take(3) { 767 | fmt.Println(i) 768 | } 769 | ``` 770 | 771 | 772 | > [!WARNING] 773 | > This iterator yields an infinite number of values and care should be taken when consuming it 774 | > otherwise it's likely to result in an infinite while loop. Consider bounding the size of the 775 | > iterator before consuming (e.g. using [Take](#take)). 776 | 777 | 778 | > [!WARNING] 779 | > There is no protection against overflowing whatever integer type is used for this iterator. 780 | 781 | ### Once 782 | 783 | Once yields the provided value once before being exhausted. 784 | 785 | ```go 786 | slices.Collect(it.Once(42)) 787 | 788 | // Chainable 789 | itx.Once(42).Collect() 790 | 791 | // For iter.Seq2 792 | maps.Collect(it.Once2(42, "42")) 793 | 794 | // As above, but chainable 795 | itx.Once(42, "42").Collect() 796 | ``` 797 | 798 | ### Repeat 799 | 800 | Repeat yields the same value indefinitely. 801 | 802 | ```go 803 | slices.Collect(it.Take(it.Repeat(42), 5)) 804 | 805 | // Chainable 806 | itx.Repeat(42).Take(5).Collect() 807 | 808 | // For iter.Seq2 809 | maps.Collect(it.Take2(it.Repeat(42, "42"), 5)) 810 | 811 | // As above, but chainable 812 | itx.Repeat2(42, "42").Take(5).Collect() 813 | ``` 814 | 815 | 816 | > [!WARNING] 817 | > This iterator yields an infinite number of values and care should be taken when consuming it 818 | > otherwise it's likely to result in an infinite while loop. Consider bounding the size of the 819 | > iterator before consuming (e.g. using [Take](#take)). 820 | 821 |

Take & TakeWhile

822 | 823 | Take limits the number of values of an iterator to a specified size. If the iterator has fewer 824 | values than the provided number then it behaves as though the original iterator is not changed. 825 | 826 | ```go 827 | slices.Collect(it.Take(slices.Values([]int{1, 2, 3}), 2)) 828 | 829 | // Chainable 830 | itx.FromSlice([]int{1, 2, 3}).Take(2).Collect() 831 | 832 | // Taking from iter.Seq2 833 | maps.Collect(it.Take2(slices.All([]int{1, 2, 3}), 2)) 834 | 835 | // As above, but chainable 836 | itx.FromSlice([]int{1, 2, 3}).Enumerate().Take(2).Collect() 837 | ``` 838 | 839 | TakeWhile yields values from the provided iterator whilst the predicate returns true for each value. 840 | After the first value results in the predicate returning false, the iterator is exhausted. 841 | 842 | ```go 843 | slices.Collect(it.TakeWhile(slices.Values([]int{1, 2, 3}), filter.LessThan(3))) 844 | 845 | // Chainable 846 | itx.FromSlice([]int{1, 2, 3}).TakeWhile(filter.LessThan(3)).Collect() 847 | 848 | lessThanThree := func(string, number int) { return number < 3 } 849 | 850 | // Taking from iter.Seq2 851 | maps.Collect(it.TakeWhile2(maps.All(map[string]int{"one": 1, "four": 4}), lessThanThree)) 852 | 853 | // As above, but chainable 854 | itx.FromMap(map[string]int{"one": 1, "four": 4}).TakeWhile(lessThanThree).Collect() 855 | ``` 856 | 857 | ### Zip, Left & Right 858 | 859 | `Zip` yields pairs of values from two iterators. It is exhausted when one of the two provided 860 | iterators is exhausted. 861 | 862 | ```go 863 | numbers := []int{1, 2, 3} 864 | strings := []string{"one", "two", "three"} 865 | 866 | maps.Collect(it.Zip(slices.Values(numbers), slices.Values(strings))) 867 | ``` 868 | 869 | `Left` and `Right` are functions that discard the left or right values of an 870 | [iter.Seq2](https://pkg.go.dev/iter#Seq2). 871 | 872 | ```go 873 | slices.Collect(it.Left(slices.All([]int{1, 2, 3}))) 874 | slices.Collect(it.Right(slices.All([]int{1, 2, 3}))) 875 | 876 | // Chainable 877 | itx.FromSlice([]int{1, 2, 3}).Enumerate().Left().Collect() 878 | itx.FromSlice([]int{1, 2, 3}).Enumerate().Right().Collect() 879 | ``` 880 | 881 | 882 | > [!TIP] 883 | > A common pattern when working with `Zip` iterators in other languages is to fill excess values 884 | > with some constant value. This pattern can easily to replicated here by using [Chain](#chain) and 885 | > [Repeat](#repeat): 886 | > 887 | > ```go 888 | > numbers := itx.FromSlice([]int{1, 2, 3, 4}) 889 | > strings := itx.FromSlice([]string{"one", "two"}) 890 | > 891 | > it.Zip(numbers, strings.Chain(it.Repeat("missing"))) 892 | > ``` 893 | 894 | 895 | > [!NOTE] 896 | > The `itx` package does not contain `Zip` due to limitations with Go's type system. 897 | 898 | ## Attributions 899 | 900 | In addition to those listed as code contributors on this repository, I'd like to also thank: 901 | 902 | - [**_Henry Wong_**](https://www.behance.net/henrywong) for designing the banner images and 903 | stickers. 904 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | go-functional is currently in pre-release. The only supported version is the latest version. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please send [an email](mailto:tomgodkin@pm.me) to the maintainer. 10 | 11 | Thank you for your report, genuine vulnerabilities reported shall grant you credit as a contributor. 12 | Please provide your GitHub username when submitting an issue if you wish to be credited when a new 13 | version is released that fixes the issue. 14 | -------------------------------------------------------------------------------- /assets/banner_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BooleanCat/go-functional/b2515648ef035e8cff6e36909a1f3e95de9d0dd8/assets/banner_dark.png -------------------------------------------------------------------------------- /assets/banner_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BooleanCat/go-functional/b2515648ef035e8cff6e36909a1f3e95de9d0dd8/assets/banner_light.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/BooleanCat/go-functional/v2 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /internal/assert/assert.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "slices" 5 | "testing" 6 | ) 7 | 8 | func True(t *testing.T, value bool) { 9 | t.Helper() 10 | 11 | if !value { 12 | t.Error("expected true") 13 | } 14 | } 15 | 16 | func False(t *testing.T, value bool) { 17 | t.Helper() 18 | 19 | if value { 20 | t.Error("expected false") 21 | } 22 | } 23 | 24 | func Equal[T comparable](t *testing.T, a, b T) { 25 | t.Helper() 26 | 27 | if a != b { 28 | t.Errorf("expected `%v` to equal `%v`", a, b) 29 | } 30 | } 31 | 32 | func SliceEqual[T comparable](t *testing.T, a, b []T) { 33 | t.Helper() 34 | 35 | if !slices.Equal(a, b) { 36 | t.Errorf("expected `%v` to equal `%v`", a, b) 37 | } 38 | } 39 | 40 | func Empty[E any, Slice ~[]E | ~string](t *testing.T, items Slice) { 41 | t.Helper() 42 | 43 | if len(items) != 0 { 44 | t.Errorf("expected `%v` to be empty", items) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/fakes/fakes.go: -------------------------------------------------------------------------------- 1 | package fakes 2 | 3 | //go:generate counterfeiter -generate 4 | 5 | //counterfeiter:generate --fake-name Reader -o . io.Reader 6 | -------------------------------------------------------------------------------- /internal/fakes/reader.go: -------------------------------------------------------------------------------- 1 | // Code generated by counterfeiter. DO NOT EDIT. 2 | package fakes 3 | 4 | import ( 5 | "io" 6 | "sync" 7 | ) 8 | 9 | type Reader struct { 10 | ReadStub func([]byte) (int, error) 11 | readMutex sync.RWMutex 12 | readArgsForCall []struct { 13 | arg1 []byte 14 | } 15 | readReturns struct { 16 | result1 int 17 | result2 error 18 | } 19 | readReturnsOnCall map[int]struct { 20 | result1 int 21 | result2 error 22 | } 23 | invocations map[string][][]interface{} 24 | invocationsMutex sync.RWMutex 25 | } 26 | 27 | func (fake *Reader) Read(arg1 []byte) (int, error) { 28 | var arg1Copy []byte 29 | if arg1 != nil { 30 | arg1Copy = make([]byte, len(arg1)) 31 | copy(arg1Copy, arg1) 32 | } 33 | fake.readMutex.Lock() 34 | ret, specificReturn := fake.readReturnsOnCall[len(fake.readArgsForCall)] 35 | fake.readArgsForCall = append(fake.readArgsForCall, struct { 36 | arg1 []byte 37 | }{arg1Copy}) 38 | stub := fake.ReadStub 39 | fakeReturns := fake.readReturns 40 | fake.recordInvocation("Read", []interface{}{arg1Copy}) 41 | fake.readMutex.Unlock() 42 | if stub != nil { 43 | return stub(arg1) 44 | } 45 | if specificReturn { 46 | return ret.result1, ret.result2 47 | } 48 | return fakeReturns.result1, fakeReturns.result2 49 | } 50 | 51 | func (fake *Reader) ReadCallCount() int { 52 | fake.readMutex.RLock() 53 | defer fake.readMutex.RUnlock() 54 | return len(fake.readArgsForCall) 55 | } 56 | 57 | func (fake *Reader) ReadCalls(stub func([]byte) (int, error)) { 58 | fake.readMutex.Lock() 59 | defer fake.readMutex.Unlock() 60 | fake.ReadStub = stub 61 | } 62 | 63 | func (fake *Reader) ReadArgsForCall(i int) []byte { 64 | fake.readMutex.RLock() 65 | defer fake.readMutex.RUnlock() 66 | argsForCall := fake.readArgsForCall[i] 67 | return argsForCall.arg1 68 | } 69 | 70 | func (fake *Reader) ReadReturns(result1 int, result2 error) { 71 | fake.readMutex.Lock() 72 | defer fake.readMutex.Unlock() 73 | fake.ReadStub = nil 74 | fake.readReturns = struct { 75 | result1 int 76 | result2 error 77 | }{result1, result2} 78 | } 79 | 80 | func (fake *Reader) ReadReturnsOnCall(i int, result1 int, result2 error) { 81 | fake.readMutex.Lock() 82 | defer fake.readMutex.Unlock() 83 | fake.ReadStub = nil 84 | if fake.readReturnsOnCall == nil { 85 | fake.readReturnsOnCall = make(map[int]struct { 86 | result1 int 87 | result2 error 88 | }) 89 | } 90 | fake.readReturnsOnCall[i] = struct { 91 | result1 int 92 | result2 error 93 | }{result1, result2} 94 | } 95 | 96 | func (fake *Reader) Invocations() map[string][][]interface{} { 97 | fake.invocationsMutex.RLock() 98 | defer fake.invocationsMutex.RUnlock() 99 | fake.readMutex.RLock() 100 | defer fake.readMutex.RUnlock() 101 | copiedInvocations := map[string][][]interface{}{} 102 | for key, value := range fake.invocations { 103 | copiedInvocations[key] = value 104 | } 105 | return copiedInvocations 106 | } 107 | 108 | func (fake *Reader) recordInvocation(key string, args []interface{}) { 109 | fake.invocationsMutex.Lock() 110 | defer fake.invocationsMutex.Unlock() 111 | if fake.invocations == nil { 112 | fake.invocations = map[string][][]interface{}{} 113 | } 114 | if fake.invocations[key] == nil { 115 | fake.invocations[key] = [][]interface{}{} 116 | } 117 | fake.invocations[key] = append(fake.invocations[key], args) 118 | } 119 | 120 | var _ io.Reader = new(Reader) 121 | -------------------------------------------------------------------------------- /it/chain.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Chain yields values from multiple iterators in sequence. 6 | func Chain[V any](iterators ...func(func(V) bool)) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for _, iterator := range iterators { 9 | iterator(yield) 10 | } 11 | } 12 | } 13 | 14 | // Chain2 yields values from multiple iterators in sequence. 15 | func Chain2[V, W any](iterators ...func(func(V, W) bool)) iter.Seq2[V, W] { 16 | return func(yield func(V, W) bool) { 17 | for _, iterator := range iterators { 18 | iterator(yield) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /it/chain_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/it" 11 | ) 12 | 13 | func ExampleChain() { 14 | numbers := slices.Collect(it.Chain(slices.Values([]int{1, 2}), slices.Values([]int{3, 4}))) 15 | 16 | fmt.Println(numbers) 17 | // Output: [1 2 3 4] 18 | } 19 | 20 | func TestChainEmpty(t *testing.T) { 21 | t.Parallel() 22 | 23 | assert.Empty[int](t, slices.Collect(it.Chain[int]())) 24 | } 25 | 26 | func TestChainMany(t *testing.T) { 27 | t.Parallel() 28 | 29 | numbers := slices.Collect(it.Chain( 30 | slices.Values([]int{1, 2}), 31 | it.Take(it.Drop(it.NaturalNumbers[int](), 3), 2), 32 | slices.Values([]int{5, 6}), 33 | )) 34 | 35 | assert.SliceEqual(t, []int{1, 2, 3, 4, 5, 6}, numbers) 36 | } 37 | 38 | func ExampleChain2() { 39 | pairs := maps.Collect(it.Chain2(maps.All(map[string]int{"a": 1}), maps.All(map[string]int{"b": 2}))) 40 | 41 | fmt.Println(len(pairs)) 42 | // Output: 2 43 | } 44 | 45 | func TestChain2(t *testing.T) { 46 | t.Parallel() 47 | 48 | pairs := maps.Collect(it.Chain2(maps.All(map[string]int{"a": 1}), maps.All(map[string]int{"b": 2}))) 49 | 50 | assert.Equal(t, 2, len(pairs)) 51 | assert.Equal(t, pairs["a"], 1) 52 | assert.Equal(t, pairs["b"], 2) 53 | } 54 | 55 | func TestChain2Empty(t *testing.T) { 56 | t.Parallel() 57 | 58 | assert.Equal(t, len(maps.Collect(it.Chain2[int, int]())), 0) 59 | } 60 | 61 | func TestChain2Many(t *testing.T) { 62 | t.Parallel() 63 | 64 | pairs := maps.Collect(it.Chain2( 65 | maps.All(map[string]int{"a": 1}), 66 | maps.All(map[string]int{"b": 2}), 67 | maps.All(map[string]int{"c": 3}), 68 | )) 69 | 70 | assert.Equal(t, 3, len(pairs)) 71 | assert.Equal(t, pairs["a"], 1) 72 | assert.Equal(t, pairs["b"], 2) 73 | assert.Equal(t, pairs["c"], 3) 74 | } 75 | -------------------------------------------------------------------------------- /it/channel.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // FromChannel yields values from a channel. 6 | // 7 | // In order to avoid a deadlock, the channel must be closed before attempting 8 | // to called `stop` on a pull-style iterator. 9 | func FromChannel[V any](channel <-chan V) iter.Seq[V] { 10 | return func(yield func(V) bool) { 11 | for value := range channel { 12 | if !yield(value) { 13 | return 14 | } 15 | } 16 | } 17 | } 18 | 19 | // ToChannel sends yielded values to a channel. 20 | // 21 | // The channel is closed when the iterator is exhausted. Beware of leaked go 22 | // routines when using this function with an infinite iterator. 23 | func ToChannel[V any](seq func(func(V) bool)) <-chan V { 24 | channel := make(chan V) 25 | 26 | go func() { 27 | defer close(channel) 28 | 29 | for value := range seq { 30 | channel <- value 31 | } 32 | }() 33 | 34 | return channel 35 | } 36 | -------------------------------------------------------------------------------- /it/channel_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/BooleanCat/go-functional/v2/internal/assert" 9 | "github.com/BooleanCat/go-functional/v2/it" 10 | ) 11 | 12 | func ExampleFromChannel() { 13 | items := make(chan int) 14 | 15 | go func() { 16 | defer close(items) 17 | items <- 1 18 | items <- 2 19 | }() 20 | 21 | for number := range it.FromChannel(items) { 22 | fmt.Println(number) 23 | } 24 | 25 | // Output: 26 | // 1 27 | // 2 28 | } 29 | 30 | func TestFromChannelYieldFalse(t *testing.T) { 31 | t.Parallel() 32 | 33 | numbersChan := make(chan int, 1) 34 | defer close(numbersChan) 35 | 36 | numbersChan <- 1 37 | numbers := it.FromChannel(numbersChan) 38 | 39 | numbers(func(value int) bool { 40 | return false 41 | }) 42 | } 43 | 44 | func TestFromChannelEmpty(t *testing.T) { 45 | t.Parallel() 46 | 47 | channel := make(chan int) 48 | close(channel) 49 | 50 | assert.Empty[int](t, slices.Collect(it.FromChannel(channel))) 51 | } 52 | 53 | func ExampleToChannel() { 54 | channel := it.ToChannel(slices.Values([]int{1, 2, 3})) 55 | 56 | for number := range channel { 57 | fmt.Println(number) 58 | } 59 | 60 | // Output: 61 | // 1 62 | // 2 63 | // 3 64 | } 65 | 66 | func TestToChannelEmpty(t *testing.T) { 67 | t.Parallel() 68 | 69 | for range it.ToChannel(slices.Values([]int{})) { 70 | t.Error("unexpected") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /it/compact.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Compact yields all values from a delegate iterator that are not zero values. 6 | func Compact[V comparable](delegate func(func(V) bool)) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for value := range delegate { 9 | var zero V 10 | if zero == value { 11 | continue 12 | } 13 | 14 | if !yield(value) { 15 | return 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /it/compact_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/BooleanCat/go-functional/v2/internal/assert" 9 | "github.com/BooleanCat/go-functional/v2/it" 10 | ) 11 | 12 | func ExampleCompact() { 13 | words := slices.Values([]string{"", "foo", "", "", "bar", ""}) 14 | fmt.Println(slices.Collect(it.Compact(words))) 15 | // Output: [foo bar] 16 | } 17 | 18 | func TestCompactEmpty(t *testing.T) { 19 | t.Parallel() 20 | 21 | words := slices.Collect(it.Compact(it.Exhausted[string]())) 22 | assert.Empty[string](t, words) 23 | } 24 | 25 | func TestCompactOnlyEmpty(t *testing.T) { 26 | t.Parallel() 27 | 28 | words := slices.Values([]string{"", "", "", ""}) 29 | assert.Empty[string](t, slices.Collect(it.Compact(words))) 30 | } 31 | 32 | func TestCompactOnlyNotEmpty(t *testing.T) { 33 | t.Parallel() 34 | 35 | words := slices.Values([]string{"foo", "bar"}) 36 | assert.SliceEqual(t, slices.Collect(it.Compact(words)), []string{"foo", "bar"}) 37 | } 38 | 39 | func TestCompactYieldFalse(t *testing.T) { 40 | t.Parallel() 41 | 42 | words := it.Compact(slices.Values([]string{"foo", "bar"})) 43 | 44 | words(func(string) bool { 45 | return false 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /it/cycle.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Cycle yields values from an iterator repeatedly. 6 | // 7 | // Note: this is an infinite iterator. 8 | // 9 | // Note: memory usage will grow until all values from the underlying iterator 10 | // are stored in memory. 11 | func Cycle[V any](delegate func(func(V) bool)) iter.Seq[V] { 12 | return func(yield func(V) bool) { 13 | var items []V 14 | 15 | for item := range delegate { 16 | items = append(items, item) 17 | if !yield(item) { 18 | break 19 | } 20 | } 21 | 22 | for { 23 | for _, item := range items { 24 | if !yield(item) { 25 | return 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | // Cycle2 yields pairs of values from an iterator repeatedly. 33 | // 34 | // Note: this is an infinite iterator. 35 | // 36 | // Note: memory usage will grow until all values from the underlying iterator 37 | // are stored in memory. 38 | func Cycle2[V, W any](delegate func(func(V, W) bool)) iter.Seq2[V, W] { 39 | return func(yield func(V, W) bool) { 40 | var items []struct { 41 | v V 42 | w W 43 | } 44 | 45 | for v, w := range delegate { 46 | items = append(items, struct { 47 | v V 48 | w W 49 | }{v, w}) 50 | if !yield(v, w) { 51 | break 52 | } 53 | } 54 | 55 | for { 56 | for _, item := range items { 57 | if !yield(item.v, item.w) { 58 | return 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /it/cycle_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/it" 11 | ) 12 | 13 | func ExampleCycle() { 14 | numbers := slices.Collect(it.Take(it.Cycle(slices.Values([]int{1, 2})), 5)) 15 | 16 | fmt.Println(numbers) 17 | // Output: [1 2 1 2 1] 18 | } 19 | 20 | func TestCycleYieldFalse(t *testing.T) { 21 | t.Parallel() 22 | 23 | numbers := it.Cycle(slices.Values([]int{1, 2})) 24 | 25 | numbers(func(value int) bool { 26 | return false 27 | }) 28 | } 29 | 30 | func ExampleCycle2() { 31 | numbers := maps.Collect(it.Take2(it.Cycle2(maps.All(map[int]string{1: "one"})), 5)) 32 | 33 | fmt.Println(numbers) 34 | // Output: map[1:one] 35 | } 36 | 37 | func TestCycle2YieldFalse(t *testing.T) { 38 | t.Parallel() 39 | 40 | numbers := it.Cycle2(maps.All(map[int]string{1: "one"})) 41 | var ( 42 | a int 43 | b string 44 | ) 45 | numbers(func(key int, value string) bool { 46 | a, b = key, value 47 | return false 48 | }) 49 | assert.Equal(t, a, 1) 50 | assert.Equal(t, b, "one") 51 | } 52 | -------------------------------------------------------------------------------- /it/drop.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Drop yields all values from a delegate iterator except the first `count` 6 | // values. 7 | func Drop[V any](delegate func(func(V) bool), count uint) iter.Seq[V] { 8 | return func(yield func(V) bool) { 9 | for value := range delegate { 10 | if count == 0 { 11 | if !yield(value) { 12 | return 13 | } 14 | } else { 15 | count-- 16 | } 17 | } 18 | } 19 | } 20 | 21 | // Drop2 yields all pairs of values from a delegate iterator except the first 22 | // `count` pairs. 23 | func Drop2[V, W any](delegate func(func(V, W) bool), count uint) iter.Seq2[V, W] { 24 | return func(yield func(V, W) bool) { 25 | for v, w := range delegate { 26 | if count == 0 { 27 | if !yield(v, w) { 28 | return 29 | } 30 | } else { 31 | count-- 32 | } 33 | } 34 | } 35 | } 36 | 37 | // DropWhile yields all values from a delegate iterator after the predicate 38 | // returns false. 39 | func DropWhile[V any](delegate func(func(V) bool), predicate func(V) bool) iter.Seq[V] { 40 | return func(yield func(V) bool) { 41 | dropped := false 42 | for value := range delegate { 43 | if !dropped && predicate(value) { 44 | continue 45 | } 46 | 47 | dropped = true 48 | if !yield(value) { 49 | return 50 | } 51 | } 52 | } 53 | } 54 | 55 | // DropWhile2 yields all pairs of values from a delegate iterator after the 56 | // predicate returns false. 57 | func DropWhile2[V, W any](delegate func(func(V, W) bool), predicate func(V, W) bool) iter.Seq2[V, W] { 58 | return func(yield func(V, W) bool) { 59 | dropped := false 60 | for v, w := range delegate { 61 | if !dropped && predicate(v, w) { 62 | continue 63 | } 64 | 65 | dropped = true 66 | if !yield(v, w) { 67 | return 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /it/drop_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/it" 11 | "github.com/BooleanCat/go-functional/v2/it/filter" 12 | ) 13 | 14 | func ExampleDrop() { 15 | for value := range it.Drop(slices.Values([]int{1, 2, 3, 4, 5}), 2) { 16 | fmt.Println(value) 17 | } 18 | 19 | // Output: 20 | // 3 21 | // 4 22 | // 5 23 | } 24 | 25 | func TestDropYieldFalse(t *testing.T) { 26 | t.Parallel() 27 | 28 | numbers := it.Drop(slices.Values([]int{1, 2, 3, 4, 5}), 2) 29 | 30 | numbers(func(value int) bool { 31 | return false 32 | }) 33 | } 34 | 35 | func TestDropEmpty(t *testing.T) { 36 | t.Parallel() 37 | 38 | assert.Empty[int](t, slices.Collect(it.Drop(it.Exhausted[int](), 2))) 39 | } 40 | 41 | func ExampleDrop2() { 42 | _, numbers := it.Collect2(it.Drop2(slices.All([]string{"zero", "one", "two"}), 1)) 43 | 44 | fmt.Println(numbers) 45 | // Output: [one two] 46 | } 47 | 48 | func TestDrop2(t *testing.T) { 49 | t.Parallel() 50 | 51 | values := []string{"zero", "one", "two"} 52 | 53 | indices, values := it.Collect2(it.Drop2(slices.All(values), 1)) 54 | 55 | assert.SliceEqual(t, indices, []int{1, 2}) 56 | assert.SliceEqual(t, values, []string{"one", "two"}) 57 | } 58 | 59 | func TestDrop2Empty(t *testing.T) { 60 | t.Parallel() 61 | 62 | assert.Equal(t, len(maps.Collect(it.Drop2(it.Exhausted2[int, int](), 1))), 0) 63 | } 64 | 65 | func TestDrop2Zero(t *testing.T) { 66 | t.Parallel() 67 | 68 | indices, values := it.Collect2(it.Drop2(slices.All([]string{"zero", "one", "two"}), 0)) 69 | 70 | assert.SliceEqual(t, indices, []int{0, 1, 2}) 71 | assert.SliceEqual(t, values, []string{"zero", "one", "two"}) 72 | } 73 | 74 | func TestDrop2YieldFalse(t *testing.T) { 75 | t.Parallel() 76 | 77 | numbers := it.Drop2(slices.All([]int{1, 2, 3}), 2) 78 | 79 | numbers(func(v, w int) bool { 80 | return false 81 | }) 82 | } 83 | 84 | func ExampleDropWhile() { 85 | for value := range it.DropWhile(slices.Values([]int{1, 2, 3, 4, 1}), filter.LessThan(3)) { 86 | fmt.Println(value) 87 | } 88 | 89 | // Output: 90 | // 3 91 | // 4 92 | // 1 93 | } 94 | 95 | func TestDropWhileYieldFalse(t *testing.T) { 96 | t.Parallel() 97 | 98 | numbers := it.DropWhile(slices.Values([]int{1, 2, 3, 4, 1}), filter.LessThan(3)) 99 | 100 | numbers(func(v int) bool { 101 | return false 102 | }) 103 | } 104 | 105 | func TestDropWhileEmpty(t *testing.T) { 106 | t.Parallel() 107 | 108 | assert.Empty[int](t, slices.Collect(it.DropWhile(it.Exhausted[int](), filter.Passthrough))) 109 | } 110 | 111 | func TestDropWhileNeverDrop(t *testing.T) { 112 | t.Parallel() 113 | 114 | numbers := slices.Collect(it.DropWhile(slices.Values([]int{1, 2, 3}), func(int) bool { return false })) 115 | assert.SliceEqual(t, []int{1, 2, 3}, numbers) 116 | } 117 | 118 | func TestDropWhileDropAll(t *testing.T) { 119 | t.Parallel() 120 | 121 | numbers := slices.Collect(it.DropWhile(slices.Values([]int{1, 2, 3}), filter.LessThan(4))) 122 | assert.Empty[int](t, numbers) 123 | } 124 | 125 | func ExampleDropWhile2() { 126 | _, values := it.Collect2(it.DropWhile2(slices.All([]int{1, 2, 3}), func(int, v int) bool { 127 | return v < 3 128 | })) 129 | 130 | fmt.Println(values) 131 | // Output: [3] 132 | } 133 | 134 | func TestDropWhile2YieldFalse(t *testing.T) { 135 | t.Parallel() 136 | 137 | numbers := it.DropWhile2(slices.All([]int{1, 2, 3}), func(int, v int) bool { 138 | return v < 3 139 | }) 140 | 141 | numbers(func(int, v int) bool { 142 | return false 143 | }) 144 | } 145 | 146 | func TestDropWhile2Empty(t *testing.T) { 147 | t.Parallel() 148 | 149 | _, values := it.Collect2(it.DropWhile2(it.Exhausted2[int, int](), filter.Passthrough2)) 150 | 151 | assert.Empty[int](t, values) 152 | } 153 | 154 | func TestDropWhile2NeverDrop(t *testing.T) { 155 | t.Parallel() 156 | 157 | _, values := it.Collect2(it.DropWhile2(slices.All([]int{1, 2, 3}), func(int, int) bool { return false })) 158 | assert.SliceEqual(t, []int{1, 2, 3}, values) 159 | } 160 | 161 | func TestDropWhile2DropAll(t *testing.T) { 162 | t.Parallel() 163 | 164 | _, values := it.Collect2(it.DropWhile2(slices.All([]int{1, 2, 3}), func(int, int) bool { return true })) 165 | assert.Empty[int](t, values) 166 | } 167 | -------------------------------------------------------------------------------- /it/enumerate.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Enumerate yields pairs of indices and values from an iterator. 6 | func Enumerate[V any](delegate func(func(V) bool)) iter.Seq2[int, V] { 7 | return func(yield func(int, V) bool) { 8 | count := 0 9 | 10 | for value := range delegate { 11 | if !yield(count, value) { 12 | return 13 | } 14 | 15 | count++ 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /it/enumerate_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/BooleanCat/go-functional/v2/it" 9 | ) 10 | 11 | func ExampleEnumerate() { 12 | for index, value := range it.Enumerate(slices.Values([]int{1, 2, 3})) { 13 | fmt.Println(index, value) 14 | } 15 | 16 | // Output: 17 | // 0 1 18 | // 1 2 19 | // 2 3 20 | } 21 | 22 | func TestEnumerateYieldFalse(t *testing.T) { 23 | t.Parallel() 24 | 25 | iterator := it.Enumerate(slices.Values([]int{1, 2, 3, 4, 5})) 26 | 27 | iterator(func(i int, n int) bool { 28 | return false 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /it/exhausted.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Exhausted is an iterator that yields no values. 6 | func Exhausted[V any]() iter.Seq[V] { 7 | return func(yield func(V) bool) {} 8 | } 9 | 10 | // Exhausted2 is an iterator that yields no values. 11 | func Exhausted2[V, W any]() iter.Seq2[V, W] { 12 | return func(yield func(V, W) bool) {} 13 | } 14 | -------------------------------------------------------------------------------- /it/exhausted_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | 8 | "github.com/BooleanCat/go-functional/v2/it" 9 | ) 10 | 11 | func ExampleExhausted() { 12 | fmt.Println(len(slices.Collect(it.Exhausted[int]()))) 13 | // Output: 0 14 | } 15 | 16 | func ExampleExhausted2() { 17 | fmt.Println(len(maps.Collect(it.Exhausted2[int, string]()))) 18 | // Output: 0 19 | } 20 | -------------------------------------------------------------------------------- /it/filter.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Filter yields values from an iterator that satisfy a predicate. 6 | func Filter[V any](delegate func(func(V) bool), predicate func(V) bool) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for value := range delegate { 9 | if predicate(value) { 10 | if !yield(value) { 11 | return 12 | } 13 | } 14 | } 15 | } 16 | } 17 | 18 | // Exclude yields values from an iterator that do not satisfy a predicate. 19 | func Exclude[V any](delegate func(func(V) bool), predicate func(V) bool) iter.Seq[V] { 20 | return Filter(delegate, not(predicate)) 21 | } 22 | 23 | // Filter2 yields values from an iterator that satisfy a predicate. 24 | func Filter2[V, W any](delegate func(func(V, W) bool), predicate func(V, W) bool) iter.Seq2[V, W] { 25 | return func(yield func(V, W) bool) { 26 | for k, v := range delegate { 27 | if predicate(k, v) { 28 | if !yield(k, v) { 29 | return 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | // Exclude2 yields values from an iterator that do not satisfy a predicate. 37 | func Exclude2[V, W any](delegate func(func(V, W) bool), predicate func(V, W) bool) iter.Seq2[V, W] { 38 | return Filter2(delegate, not2(predicate)) 39 | } 40 | 41 | // FilterError yields values from an iterator that satisfy a predicate where 42 | // the predicate can return an error. 43 | func FilterError[V any](delegate func(func(V) bool), predicate func(V) (bool, error)) iter.Seq2[V, error] { 44 | return func(yield func(V, error) bool) { 45 | for value := range delegate { 46 | if ok, err := predicate(value); err != nil { 47 | var zero V 48 | if !yield(zero, err) { 49 | return 50 | } 51 | } else if ok { 52 | if !yield(value, nil) { 53 | return 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | // ExcludeError yields values from an iterator that do not satisfy a predicate 61 | // where the predicate can return an error. 62 | func ExcludeError[V any](delegate func(func(V) bool), predicate func(V) (bool, error)) iter.Seq2[V, error] { 63 | return FilterError(delegate, notError(predicate)) 64 | } 65 | 66 | func not[V any](predicate func(V) bool) func(V) bool { 67 | return func(value V) bool { 68 | return !predicate(value) 69 | } 70 | } 71 | 72 | func not2[V, W any](predicate func(V, W) bool) func(V, W) bool { 73 | return func(k V, v W) bool { 74 | return !predicate(k, v) 75 | } 76 | } 77 | 78 | func notError[V any](predicate func(V) (bool, error)) func(V) (bool, error) { 79 | return func(value V) (bool, error) { 80 | ok, err := predicate(value) 81 | return !ok, err 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /it/filter/filter.go: -------------------------------------------------------------------------------- 1 | // This package contains functions intended for use with [iter.Filter]. 2 | package filter 3 | 4 | import ( 5 | "bytes" 6 | "cmp" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // IsEven returns true when the provided integer is even. 12 | func IsEven[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64](integer T) bool { 13 | return integer%2 == 0 14 | } 15 | 16 | // IsOdd returns true when the provided integer is odd. 17 | func IsOdd[T ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64](integer T) bool { 18 | return integer%2 != 0 19 | } 20 | 21 | // IsEqual returns a function that returns true when the provided value is equal 22 | // to some value. 23 | func IsEqual[T comparable](value T) func(T) bool { 24 | return func(candidate T) bool { 25 | return candidate == value 26 | } 27 | } 28 | 29 | // NotEqual returns a function that returns true when the provided value is not 30 | // equal to some value. 31 | func NotEqual[T comparable](value T) func(T) bool { 32 | return func(candidate T) bool { 33 | return candidate != value 34 | } 35 | } 36 | 37 | // IsZero returns true when the provided value is the zero value for its type. 38 | func IsZero[T comparable](value T) bool { 39 | var zero T 40 | return value == zero 41 | } 42 | 43 | // GreaterThan returns a function that returns true when the provided value is 44 | // greater than a threshold. 45 | func GreaterThan[T cmp.Ordered](threshold T) func(T) bool { 46 | return func(value T) bool { 47 | return value > threshold 48 | } 49 | } 50 | 51 | // LessThan returns a function that returns true when the provided value is less 52 | // than a threshold. 53 | func LessThan[T cmp.Ordered](threshold T) func(T) bool { 54 | return func(value T) bool { 55 | return value < threshold 56 | } 57 | } 58 | 59 | // Passthrough returns a function that returns true for any value. 60 | func Passthrough[V any](value V) bool { 61 | return true 62 | } 63 | 64 | // Passthrough2 returns a function that returns true for any pair of values. 65 | func Passthrough2[V, W any](v V, w W) bool { 66 | return true 67 | } 68 | 69 | // Not returns a function that inverts the result of the provided function. 70 | func Not[T any](fn func(T) bool) func(T) bool { 71 | return func(value T) bool { 72 | return !fn(value) 73 | } 74 | } 75 | 76 | // And returns a function that returns true when all provided functions return 77 | // true. 78 | func And[T any](filters ...func(T) bool) func(T) bool { 79 | return func(value T) bool { 80 | for _, filter := range filters { 81 | if !filter(value) { 82 | return false 83 | } 84 | } 85 | 86 | return true 87 | } 88 | } 89 | 90 | // Or returns a function that returns true when any of the provided functions 91 | // return true. 92 | func Or[T any](filters ...func(T) bool) func(T) bool { 93 | return func(value T) bool { 94 | for _, filter := range filters { 95 | if filter(value) { 96 | return true 97 | } 98 | } 99 | 100 | return false 101 | } 102 | } 103 | 104 | // Match returns a function that returns true when the provided string or byte 105 | // slice matches a pattern. See [regexp.MatchString]. 106 | func Match[T string | []byte](pattern *regexp.Regexp) func(T) bool { 107 | return func(term T) bool { 108 | return pattern.MatchString(string(term)) 109 | } 110 | } 111 | 112 | // Contains returns a function that returns true when the provided string or 113 | // byte slice is found within another string or byte slice. See 114 | // [strings.Contains] and [bytes.Contains]. 115 | func Contains[T string | []byte](t T) func(T) bool { 116 | if s, ok := any(t).(string); ok { 117 | return func(v T) bool { return strings.Contains(any(v).(string), s) } 118 | } 119 | 120 | return func(v T) bool { return bytes.Contains(any(v).([]byte), any(t).([]byte)) } 121 | } 122 | -------------------------------------------------------------------------------- /it/filter/filter_test.go: -------------------------------------------------------------------------------- 1 | package filter_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "regexp" 7 | "slices" 8 | 9 | "github.com/BooleanCat/go-functional/v2/it" 10 | "github.com/BooleanCat/go-functional/v2/it/filter" 11 | ) 12 | 13 | func ExampleIsEven() { 14 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.IsEven) { 15 | fmt.Println(number) 16 | } 17 | 18 | // Output: 19 | // 2 20 | // 4 21 | } 22 | 23 | func ExampleIsOdd() { 24 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.IsOdd) { 25 | fmt.Println(number) 26 | } 27 | 28 | // Output: 29 | // 1 30 | // 3 31 | } 32 | 33 | func ExampleIsEqual() { 34 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.IsEqual(2)) { 35 | fmt.Println(number) 36 | } 37 | 38 | // Output: 2 39 | } 40 | 41 | func ExampleNotEqual() { 42 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.NotEqual(2)) { 43 | fmt.Println(number) 44 | } 45 | 46 | // Output: 47 | // 1 48 | // 3 49 | // 4 50 | } 51 | 52 | func ExampleIsZero() { 53 | for number := range it.Filter(slices.Values([]int{0, 1, 2, 3}), filter.IsZero) { 54 | fmt.Println(number) 55 | } 56 | 57 | // Output: 0 58 | } 59 | 60 | func ExampleGreaterThan() { 61 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.GreaterThan(2)) { 62 | fmt.Println(number) 63 | } 64 | 65 | // Output: 66 | // 3 67 | // 4 68 | } 69 | 70 | func ExampleLessThan() { 71 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4}), filter.LessThan(3)) { 72 | fmt.Println(number) 73 | } 74 | 75 | // Output: 76 | // 1 77 | // 2 78 | } 79 | 80 | func ExamplePassthrough() { 81 | for number := range it.Filter(slices.Values([]int{1, 2, 3}), filter.Passthrough) { 82 | fmt.Println(number) 83 | } 84 | 85 | // Output: 86 | // 1 87 | // 2 88 | // 3 89 | } 90 | 91 | func ExamplePassthrough2() { 92 | for key, value := range it.Filter2(maps.All(map[int]string{1: "two"}), filter.Passthrough2) { 93 | fmt.Println(key, value) 94 | } 95 | 96 | // Output: 1 two 97 | } 98 | 99 | func ExampleNot() { 100 | numbers := slices.Values([]int{1, 2, 3, 4}) 101 | 102 | for number := range it.Filter(numbers, filter.Not[int](filter.IsEven)) { 103 | fmt.Println(number) 104 | } 105 | 106 | // Output: 107 | // 1 108 | // 3 109 | } 110 | 111 | func ExampleAnd() { 112 | numbers := slices.Values([]int{1, 2, 3, 4}) 113 | 114 | for number := range it.Filter(numbers, filter.And(filter.IsOdd, filter.GreaterThan(2))) { 115 | fmt.Println(number) 116 | } 117 | 118 | // Output: 3 119 | } 120 | 121 | func ExampleOr() { 122 | numbers := slices.Values([]int{1, 2, 3, 4}) 123 | 124 | for number := range it.Filter(numbers, filter.Or(filter.IsEven, filter.LessThan(3))) { 125 | fmt.Println(number) 126 | } 127 | 128 | // Output: 129 | // 1 130 | // 2 131 | // 4 132 | } 133 | 134 | func ExampleMatch_string() { 135 | pattern := regexp.MustCompile(`^foo`) 136 | 137 | strings := slices.Values([]string{"foobar", "barfoo"}) 138 | 139 | for match := range it.Filter(strings, filter.Match[string](pattern)) { 140 | fmt.Println(match) 141 | } 142 | 143 | // Output: foobar 144 | } 145 | 146 | func ExampleMatch_bytes() { 147 | pattern := regexp.MustCompile(`^foo`) 148 | 149 | strings := slices.Values([][]byte{[]byte("foobar"), []byte("barfoo")}) 150 | 151 | for match := range it.Filter(strings, filter.Match[[]byte](pattern)) { 152 | fmt.Println(string(match)) 153 | } 154 | 155 | // Output: foobar 156 | } 157 | 158 | func ExampleContains_string() { 159 | strings := slices.Values([]string{"foobar", "barfoo"}) 160 | 161 | for element := range it.Filter(strings, filter.Contains("rfoo")) { 162 | fmt.Println(element) 163 | } 164 | 165 | // Output: barfoo 166 | } 167 | 168 | func ExampleContains_bytes() { 169 | strings := slices.Values([][]byte{[]byte("foobar"), []byte("barfoo")}) 170 | 171 | for element := range it.Filter(strings, filter.Contains([]byte("rfoo"))) { 172 | fmt.Println(string(element)) 173 | } 174 | 175 | // Output: barfoo 176 | } 177 | -------------------------------------------------------------------------------- /it/filter_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "maps" 7 | "slices" 8 | "testing" 9 | 10 | "github.com/BooleanCat/go-functional/v2/internal/assert" 11 | "github.com/BooleanCat/go-functional/v2/it" 12 | "github.com/BooleanCat/go-functional/v2/it/filter" 13 | ) 14 | 15 | func ExampleFilter() { 16 | for number := range it.Filter(slices.Values([]int{1, 2, 3, 4, 5}), filter.IsEven) { 17 | fmt.Println(number) 18 | } 19 | 20 | // Output: 21 | // 2 22 | // 4 23 | } 24 | 25 | func TestFilterEmpty(t *testing.T) { 26 | t.Parallel() 27 | 28 | assert.Empty[int](t, slices.Collect(it.Filter(it.Exhausted[int](), filter.IsEven))) 29 | } 30 | 31 | func TestFilterYieldsFalse(t *testing.T) { 32 | t.Parallel() 33 | 34 | seq := it.Filter(slices.Values([]int{1, 2, 3, 4, 5}), filter.IsEven) 35 | 36 | var value int 37 | 38 | seq(func(v int) bool { 39 | value = v 40 | return false 41 | }) 42 | 43 | assert.Equal(t, value, 2) 44 | } 45 | 46 | func ExampleExclude() { 47 | for number := range it.Exclude(slices.Values([]int{1, 2, 3, 4, 5}), filter.IsEven) { 48 | fmt.Println(number) 49 | } 50 | 51 | // Output: 52 | // 1 53 | // 3 54 | // 5 55 | } 56 | 57 | func ExampleFilter2() { 58 | isOne := func(n int, _ string) bool { return n == 1 } 59 | numbers := map[int]string{1: "one", 2: "two", 3: "three"} 60 | 61 | for key, value := range it.Filter2(maps.All(numbers), isOne) { 62 | fmt.Println(key, value) 63 | } 64 | 65 | // Output: 1 one 66 | } 67 | 68 | func TestFilter2Empty(t *testing.T) { 69 | t.Parallel() 70 | 71 | assert.Equal(t, len(maps.Collect(it.Filter2(it.Exhausted2[int, int](), filter.Passthrough2))), 0) 72 | } 73 | 74 | func TestFilter2YieldsFalse(t *testing.T) { 75 | t.Parallel() 76 | 77 | seq := it.Filter2(maps.All(map[int]string{1: "one", 2: "two"}), filter.Passthrough2) 78 | 79 | seq(func(k int, v string) bool { 80 | return false 81 | }) 82 | } 83 | 84 | func ExampleExclude2() { 85 | isOne := func(n int, _ string) bool { return n == 1 } 86 | numbers := map[int]string{1: "one", 3: "three"} 87 | 88 | for key, value := range it.Exclude2(maps.All(numbers), isOne) { 89 | fmt.Println(key, value) 90 | } 91 | 92 | // Output: 3 three 93 | } 94 | 95 | func TestExclude2Empty(t *testing.T) { 96 | t.Parallel() 97 | 98 | assert.Equal(t, len(maps.Collect(it.Exclude2(it.Exhausted2[int, int](), filter.Passthrough2))), 0) 99 | } 100 | 101 | func ExampleFilterError() { 102 | isFoo := func(s string) (bool, error) { return s == "foo", nil } 103 | 104 | values := slices.Values([]string{"foo", "bar", "foo"}) 105 | 106 | foos, err := it.TryCollect(it.FilterError(values, isFoo)) 107 | fmt.Println(foos, err) 108 | 109 | // Output: [foo foo] 110 | } 111 | 112 | func TestFilterErrorYieldsFalse(t *testing.T) { 113 | t.Parallel() 114 | 115 | passthrough := func(s string) (bool, error) { return true, nil } 116 | 117 | seq := it.FilterError(slices.Values([]string{"foo", "bar", "foo"}), passthrough) 118 | 119 | seq(func(v string, _ error) bool { 120 | return false 121 | }) 122 | } 123 | 124 | func TestFilterErrorError(t *testing.T) { 125 | t.Parallel() 126 | 127 | alwaysError := func(string) (bool, error) { return false, errors.New("oops") } 128 | 129 | _, err := it.TryCollect(it.FilterError(slices.Values([]string{"foo"}), alwaysError)) 130 | assert.Equal(t, err.Error(), "oops") 131 | } 132 | 133 | func TestFilterErrorErrorSometimes(t *testing.T) { 134 | t.Parallel() 135 | 136 | sometimesError := func(s string) (bool, error) { 137 | if s == "foo" { 138 | return true, errors.New("oops") 139 | } 140 | 141 | return true, nil 142 | } 143 | 144 | values, errs := it.Collect2(it.FilterError(slices.Values([]string{"foo", "bar"}), sometimesError)) 145 | assert.SliceEqual(t, values, []string{"", "bar"}) 146 | 147 | assert.Equal(t, len(errs), 2) 148 | assert.Equal(t, errs[0].Error(), "oops") 149 | assert.Equal(t, errs[1], nil) 150 | } 151 | 152 | func ExampleExcludeError() { 153 | isFoo := func(s string) (bool, error) { return s == "foo", nil } 154 | 155 | values := slices.Values([]string{"foo", "bar", "foo"}) 156 | 157 | bars, err := it.TryCollect(it.ExcludeError(values, isFoo)) 158 | fmt.Println(bars, err) 159 | 160 | // Output: [bar] 161 | } 162 | -------------------------------------------------------------------------------- /it/filter_unique.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // FilterUnique yields all the unique values from an iterator. 6 | // 7 | // Note: All unique values seen from an iterator are stored in memory. 8 | func FilterUnique[V comparable](iterator func(func(V) bool)) iter.Seq[V] { 9 | return func(yield func(V) bool) { 10 | seen := make(map[V]struct{}) 11 | 12 | for value := range iterator { 13 | if _, ok := seen[value]; ok { 14 | continue 15 | } 16 | 17 | seen[value] = struct{}{} 18 | if !yield(value) { 19 | return 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /it/filter_unique_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/BooleanCat/go-functional/v2/internal/assert" 9 | "github.com/BooleanCat/go-functional/v2/it" 10 | ) 11 | 12 | func ExampleFilterUnique() { 13 | for number := range it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4})) { 14 | fmt.Println(number) 15 | } 16 | 17 | // Output: 18 | // 1 19 | // 2 20 | // 3 21 | // 4 22 | } 23 | 24 | func TestFilterUniqueEmpty(t *testing.T) { 25 | t.Parallel() 26 | 27 | assert.Empty[int](t, slices.Collect(it.Exhausted[int]())) 28 | } 29 | 30 | func TestFilterUniqueYieldFalse(t *testing.T) { 31 | t.Parallel() 32 | 33 | iterator := it.FilterUnique(slices.Values([]int{100, 200, 300})) 34 | 35 | iterator(func(v int) bool { 36 | return false 37 | }) 38 | } 39 | 40 | func TestFilterUniqueWithNoDuplicates(t *testing.T) { 41 | t.Parallel() 42 | 43 | numbers := slices.Collect(it.FilterUnique(slices.Values([]int{1, 2, 3}))) 44 | assert.SliceEqual(t, []int{1, 2, 3}, numbers) 45 | } 46 | 47 | func TestFilterUniqueWithDuplicates(t *testing.T) { 48 | t.Parallel() 49 | 50 | strings := slices.Collect(it.FilterUnique(slices.Values([]string{"hello", "world", "hello", "world", "hello"}))) 51 | assert.SliceEqual(t, []string{"hello", "world"}, strings) 52 | } 53 | -------------------------------------------------------------------------------- /it/integers.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Integers yields all integers in the range [start, stop) with the given step. 6 | func Integers[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64](start, stop, step V) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for i := start; i < stop; i += step { 9 | if !yield(i) { 10 | return 11 | } 12 | } 13 | } 14 | } 15 | 16 | // NaturalNumbers yields all non-negative integers in ascending order. 17 | func NaturalNumbers[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64]() iter.Seq[V] { 18 | return func(yield func(V) bool) { 19 | var i V 20 | 21 | for { 22 | if !yield(i) { 23 | return 24 | } 25 | i++ 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /it/integers_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it" 8 | ) 9 | 10 | func ExampleNaturalNumbers() { 11 | for i := range it.NaturalNumbers[int]() { 12 | if i >= 3 { 13 | break 14 | } 15 | 16 | fmt.Println(i) 17 | } 18 | 19 | // Output: 20 | // 0 21 | // 1 22 | // 2 23 | } 24 | 25 | func ExampleIntegers() { 26 | for i := range it.Integers[uint](0, 5, 2) { 27 | fmt.Println(i) 28 | } 29 | 30 | // Output: 31 | // 0 32 | // 2 33 | // 4 34 | } 35 | 36 | func TestIntegersYieldFalse(t *testing.T) { 37 | t.Parallel() 38 | 39 | numbers := it.Integers[uint](0, 3, 1) 40 | 41 | numbers(func(value uint) bool { 42 | return false 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /it/iter.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import ( 4 | "cmp" 5 | "fmt" 6 | "iter" 7 | ) 8 | 9 | // ForEach consumes an iterator and applies a function to each value yielded. 10 | func ForEach[V any](iterator func(func(V) bool), fn func(V)) { 11 | for item := range iterator { 12 | fn(item) 13 | } 14 | } 15 | 16 | // ForEach2 consumes an iterator and applies a function to each pair of values. 17 | func ForEach2[V, W any](iterator func(func(V, W) bool), fn func(V, W)) { 18 | for v, w := range iterator { 19 | fn(v, w) 20 | } 21 | } 22 | 23 | // Fold will fold every element into an accumulator by applying a function and 24 | // passing an initial value. 25 | func Fold[V, R any](iterator func(func(V) bool), fn func(R, V) R, initial R) R { 26 | result := initial 27 | 28 | for item := range iterator { 29 | result = fn(result, item) 30 | } 31 | 32 | return result 33 | } 34 | 35 | // Fold2 will fold every element into an accumulator by applying a function and 36 | // passing an initial value. 37 | func Fold2[V, W, R any](iterator func(func(V, W) bool), fn func(R, V, W) R, initial R) R { 38 | result := initial 39 | 40 | for v, w := range iterator { 41 | result = fn(result, v, w) 42 | } 43 | 44 | return result 45 | } 46 | 47 | // Max consumes an iterator and returns the maximum value yielded and true if 48 | // there was at least one value, or the zero value and false if the iterator 49 | // was empty. 50 | func Max[V cmp.Ordered](iterator func(func(V) bool)) (V, bool) { 51 | var ( 52 | max V 53 | set bool 54 | ) 55 | 56 | next, _ := iter.Pull(iterator) 57 | first, ok := next() 58 | if !ok { 59 | return max, set 60 | } 61 | 62 | max = first 63 | set = true 64 | 65 | for item := range iterator { 66 | if item > max { 67 | max = item 68 | } 69 | } 70 | 71 | return max, set 72 | } 73 | 74 | // Min consumes an iterator and returns the minimum value yielded and true if 75 | // there was at least one value, or the zero value and false if the iterator 76 | // was empty. 77 | func Min[V cmp.Ordered](iterator func(func(V) bool)) (V, bool) { 78 | var ( 79 | min V 80 | set bool 81 | ) 82 | 83 | next, _ := iter.Pull(iterator) 84 | first, ok := next() 85 | if !ok { 86 | return min, set 87 | } 88 | 89 | min = first 90 | set = true 91 | 92 | for item := range iterator { 93 | if item < min { 94 | min = item 95 | } 96 | } 97 | 98 | return min, set 99 | } 100 | 101 | // Find consumes an iterator until a value is found that satisfies a predicate. 102 | // It returns the value and true if one was found, or the zero value and false 103 | // if the iterator was exhausted. 104 | func Find[V any](iterator func(func(V) bool), pred func(V) bool) (V, bool) { 105 | for item := range iterator { 106 | if pred(item) { 107 | return item, true 108 | } 109 | } 110 | 111 | var zero V 112 | return zero, false 113 | } 114 | 115 | // Find2 consumes an iterator until a pair of values is found that satisfies a 116 | // predicate. It returns the pair and true if one was found, or the zero values 117 | // and false if the iterator was exhausted. 118 | func Find2[V, W any](iterator func(func(V, W) bool), pred func(V, W) bool) (V, W, bool) { 119 | for v, w := range iterator { 120 | if pred(v, w) { 121 | return v, w, true 122 | } 123 | } 124 | 125 | var zeroV V 126 | var zeroW W 127 | return zeroV, zeroW, false 128 | } 129 | 130 | // TryCollect consumes an [iter.Seq2] where the right side yields errors and 131 | // returns a slice of values and the first error encountered. Iteration stops 132 | // at the first error. 133 | func TryCollect[V any](iterator func(func(V, error) bool)) ([]V, error) { 134 | var values []V 135 | 136 | for v, err := range iterator { 137 | if err != nil { 138 | return values, err 139 | } 140 | values = append(values, v) 141 | } 142 | 143 | return values, nil 144 | } 145 | 146 | // MustCollect consumes an [iter.Seq2] where the right side yields errors and 147 | // returns a slice of values. If an error is encountered this function will 148 | // panic. 149 | func MustCollect[V any](iterator func(func(V, error) bool)) []V { 150 | var values []V 151 | 152 | for v, err := range iterator { 153 | if err != nil { 154 | panic(fmt.Sprintf("it: MustCollect: error yielded by iterator: %s", err.Error())) 155 | } 156 | values = append(values, v) 157 | } 158 | 159 | return values 160 | } 161 | 162 | // Collect2 consumes an [iter.Seq2] and returns two slices of values. 163 | func Collect2[V, W any](iterator func(func(V, W) bool)) ([]V, []W) { 164 | var ( 165 | lefts []V 166 | rights []W 167 | ) 168 | 169 | for v, w := range iterator { 170 | lefts = append(lefts, v) 171 | rights = append(rights, w) 172 | } 173 | 174 | return lefts, rights 175 | } 176 | 177 | // Len consumes an [iter.Seq] and returns the number of values yielded. 178 | func Len[V any](iterator func(func(V) bool)) int { 179 | var length int 180 | 181 | for range iterator { 182 | length++ 183 | } 184 | 185 | return length 186 | } 187 | 188 | // Len2 consumes an [iter.Seq2] and returns the number of pairs of values 189 | // yielded. 190 | func Len2[V, W any](iterator func(func(V, W) bool)) int { 191 | var length int 192 | 193 | for range iterator { 194 | length++ 195 | } 196 | 197 | return length 198 | } 199 | 200 | // Contains consumes an [iter.Seq] until the provided value is found and 201 | // returns true. If the value is not found, it returns false when the iterator 202 | // is exhausted. 203 | func Contains[V comparable](iterator func(func(V) bool), v V) bool { 204 | for value := range iterator { 205 | if value == v { 206 | return true 207 | } 208 | } 209 | 210 | return false 211 | } 212 | 213 | // Drain consumes an [iter.Seq] completely, dropping all values. 214 | // 215 | // You may wish to use this to execute side effects without needing to collect 216 | // values. 217 | func Drain[V any](iterator func(func(V) bool)) { 218 | for range iterator { 219 | } 220 | } 221 | 222 | // Drain2 consumes an [iter.Seq2] completely, dropping all values. 223 | // 224 | // You may wish to use this to execute side effects without needing to collect 225 | // values. 226 | func Drain2[V, W any](iterator func(func(V, W) bool)) { 227 | for range iterator { 228 | } 229 | } 230 | 231 | // All consumes an [iter.Seq] of `bool`s and returns true if all values are 232 | // true, or false otherwise. Iteration will terminate early if a `false` is 233 | // encountered. 234 | func All(iterator func(func(bool) bool)) bool { 235 | for item := range iterator { 236 | if !item { 237 | return false 238 | } 239 | } 240 | 241 | return true 242 | } 243 | 244 | // All consumes an [iter.Seq] of `bool`s and returns true if any of the values 245 | // are true, or false otherwise. Iteration will terminate early if a `true` is 246 | // encountered. 247 | func Any(iterator func(func(bool) bool)) bool { 248 | for item := range iterator { 249 | if item { 250 | return true 251 | } 252 | } 253 | 254 | return false 255 | } 256 | -------------------------------------------------------------------------------- /it/iter_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "slices" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/BooleanCat/go-functional/v2/internal/assert" 11 | "github.com/BooleanCat/go-functional/v2/internal/fakes" 12 | "github.com/BooleanCat/go-functional/v2/it" 13 | "github.com/BooleanCat/go-functional/v2/it/op" 14 | ) 15 | 16 | func ExampleForEach() { 17 | it.ForEach(slices.Values([]int{1, 2, 3}), func(number int) { 18 | fmt.Println(number) 19 | }) 20 | // Output: 21 | // 1 22 | // 2 23 | // 3 24 | } 25 | 26 | func TestForEachEmpty(t *testing.T) { 27 | t.Parallel() 28 | 29 | it.ForEach(slices.Values([]int{}), func(int) { 30 | t.Error("unexpected") 31 | }) 32 | } 33 | 34 | func ExampleForEach2() { 35 | it.ForEach2(slices.All([]int{1, 2, 3}), func(index int, number int) { 36 | fmt.Println(index, number) 37 | }) 38 | // Output: 39 | // 0 1 40 | // 1 2 41 | // 2 3 42 | } 43 | 44 | func TestForEach2Empty(t *testing.T) { 45 | t.Parallel() 46 | 47 | it.ForEach2(it.Enumerate(it.Exhausted[int]()), func(int, int) { 48 | t.Error("unexpected") 49 | }) 50 | } 51 | 52 | func ExampleFold() { 53 | fmt.Println(it.Fold(slices.Values([]int{1, 2, 3}), op.Add, 0)) 54 | // Output: 6 55 | } 56 | 57 | func TestFoldEmpty(t *testing.T) { 58 | t.Parallel() 59 | 60 | assert.Equal(t, it.Fold(it.Exhausted[int](), func(int, int) int { return 0 }, 0), 0) 61 | } 62 | 63 | func ExampleFold2() { 64 | fmt.Println(it.Fold2(slices.All([]int{1, 2, 3}), func(i, a, b int) int { 65 | return i + 1 66 | }, 0)) 67 | 68 | // Output: 3 69 | } 70 | 71 | func ExampleMax() { 72 | max, ok := it.Max(slices.Values([]int{1, 2, 3})) 73 | fmt.Println(max, ok) 74 | 75 | // Output: 3 true 76 | } 77 | 78 | func TestMaxEmpty(t *testing.T) { 79 | t.Parallel() 80 | 81 | max, ok := it.Max(it.Exhausted[int]()) 82 | assert.Equal(t, max, 0) 83 | assert.False(t, ok) 84 | } 85 | 86 | func ExampleMin() { 87 | min, ok := it.Min(slices.Values([]int{4, 2, 1, 3})) 88 | fmt.Println(min, ok) 89 | 90 | // Output: 1 true 91 | } 92 | 93 | func TestMinEmpty(t *testing.T) { 94 | t.Parallel() 95 | 96 | min, ok := it.Min(it.Exhausted[int]()) 97 | assert.Equal(t, min, 0) 98 | assert.False(t, ok) 99 | } 100 | 101 | func ExampleFind() { 102 | found, ok := it.Find(slices.Values([]int{1, 2, 3}), func(i int) bool { 103 | return i == 2 104 | }) 105 | fmt.Println(found, ok) 106 | 107 | // Output: 2 true 108 | } 109 | 110 | func ExampleFind_notFound() { 111 | found, ok := it.Find(slices.Values([]int{1, 2, 3}), func(i int) bool { 112 | return i == 4 113 | }) 114 | fmt.Println(found, ok) 115 | 116 | // Output: 0 false 117 | } 118 | 119 | func ExampleFind2() { 120 | index, value, ok := it.Find2(slices.All([]int{1, 2, 3}), func(i, v int) bool { 121 | return i == 2 122 | }) 123 | fmt.Println(index, value, ok) 124 | 125 | // Output: 2 3 true 126 | } 127 | 128 | func ExampleFind2_notFound() { 129 | index, value, ok := it.Find2(slices.All([]int{1, 2, 3}), func(i, v int) bool { 130 | return i == 4 131 | }) 132 | 133 | fmt.Println(index, value, ok) 134 | // Output: 0 0 false 135 | } 136 | 137 | func ExampleCollect2() { 138 | indicies, values := it.Collect2(slices.All([]int{1, 2, 3})) 139 | fmt.Println(values) 140 | fmt.Println(indicies) 141 | 142 | // Output: 143 | // [1 2 3] 144 | // [0 1 2] 145 | } 146 | 147 | func ExampleTryCollect() { 148 | text := strings.NewReader("one\ntwo\nthree\n") 149 | 150 | lines, err := it.TryCollect(it.LinesString(text)) 151 | fmt.Println(err) 152 | fmt.Println(lines) 153 | 154 | // Output: 155 | // 156 | // [one two three] 157 | } 158 | 159 | func TestTryCollectError(t *testing.T) { 160 | t.Parallel() 161 | 162 | reader := new(fakes.Reader) 163 | reader.ReadReturns(0, errors.New("read error")) 164 | 165 | lines, err := it.TryCollect(it.LinesString(reader)) 166 | 167 | assert.Equal(t, err.Error(), "read error") 168 | assert.Empty[string](t, lines) 169 | } 170 | 171 | func ExampleLen() { 172 | fmt.Println(it.Len(slices.Values([]int{1, 2, 3}))) 173 | 174 | // Output: 3 175 | } 176 | 177 | func TestLenEmpty(t *testing.T) { 178 | t.Parallel() 179 | 180 | assert.Equal(t, it.Len(it.Exhausted[int]()), 0) 181 | } 182 | 183 | func ExampleLen2() { 184 | fmt.Println(it.Len2(slices.All([]int{1, 2, 3}))) 185 | 186 | // Output: 3 187 | } 188 | 189 | func TestLen2Empty(t *testing.T) { 190 | t.Parallel() 191 | 192 | assert.Equal(t, it.Len2(it.Enumerate(it.Exhausted[int]())), 0) 193 | } 194 | 195 | func ExampleContains() { 196 | numbers := slices.Values([]int{1, 2, 3}) 197 | fmt.Println(it.Contains(numbers, 2)) 198 | // Output: true 199 | } 200 | 201 | func TestContainsFalse(t *testing.T) { 202 | assert.False(t, it.Contains(slices.Values([]int{1, 2, 3}), 4)) 203 | } 204 | 205 | func ExampleDrain() { 206 | numbers := it.Map(slices.Values([]int{1, 2, 3}), func(n int) int { 207 | fmt.Println(n) 208 | return n 209 | }) 210 | 211 | it.Drain(numbers) 212 | 213 | // Output: 214 | // 1 215 | // 2 216 | // 3 217 | } 218 | 219 | func ExampleDrain2() { 220 | numbers := it.Map2(slices.All([]int{1, 2, 3}), func(i, n int) (int, int) { 221 | fmt.Println(n) 222 | return i, n 223 | }) 224 | 225 | it.Drain2(numbers) 226 | 227 | // Output: 228 | // 1 229 | // 2 230 | // 3 231 | } 232 | 233 | func ExampleMustCollect() { 234 | buffer := strings.NewReader("one\ntwo") 235 | lines := it.MustCollect(it.LinesString(buffer)) 236 | 237 | fmt.Println(lines) 238 | // Output: [one two] 239 | } 240 | 241 | func TestMustCollectPanic(t *testing.T) { 242 | t.Parallel() 243 | 244 | defer func() { 245 | r := recover() 246 | 247 | if r == nil { 248 | t.Errorf("expected panic") 249 | } 250 | 251 | if fmt.Sprint(r) != "it: MustCollect: error yielded by iterator: read error" { 252 | t.Errorf("wrong panic message") 253 | } 254 | }() 255 | 256 | reader := new(fakes.Reader) 257 | reader.ReadReturns(0, errors.New("read error")) 258 | 259 | it.MustCollect(it.LinesString(reader)) 260 | } 261 | 262 | func ExampleAll() { 263 | truths := []bool{true, true, true} 264 | fmt.Println(it.All(slices.Values(truths))) 265 | // Output: true 266 | } 267 | 268 | func TestAllEmpty(t *testing.T) { 269 | assert.True(t, it.All(it.Exhausted[bool]())) 270 | } 271 | 272 | func TestAllWithFalse(t *testing.T) { 273 | assert.False(t, it.All(slices.Values([]bool{true, false, true}))) 274 | } 275 | 276 | func ExampleAny() { 277 | aTrue := []bool{false, true, false} 278 | fmt.Println(it.Any(slices.Values(aTrue))) 279 | // Output: true 280 | } 281 | 282 | func TestAnyEmpty(t *testing.T) { 283 | assert.False(t, it.Any(it.Exhausted[bool]())) 284 | } 285 | 286 | func TestAnyAllFalse(t *testing.T) { 287 | assert.False(t, it.Any(slices.Values([]bool{false, false}))) 288 | } 289 | -------------------------------------------------------------------------------- /it/itx/chain.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import ( 4 | "slices" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it" 7 | ) 8 | 9 | // Chain is a convenience method for chaining [it.Chain] on [Iterator]s. 10 | func (iterator Iterator[V]) Chain(iterators ...func(func(V) bool)) Iterator[V] { 11 | iter := (func(func(V) bool))(iterator) 12 | return Iterator[V](it.Chain(slices.Insert(iterators, 0, iter)...)) 13 | } 14 | 15 | // Chain is a convenience method for chaining [it.Chain2] on [Iterator2]s. 16 | func (iterator Iterator2[V, W]) Chain(iterators ...func(func(V, W) bool)) Iterator2[V, W] { 17 | iter := (func(func(V, W) bool))(iterator) 18 | return Iterator2[V, W](it.Chain2(slices.Insert(iterators, 0, iter)...)) 19 | } 20 | -------------------------------------------------------------------------------- /it/itx/chain_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleIterator_Chain() { 11 | numbers := itx.FromSlice([]int{1, 2}).Chain(itx.FromSlice([]int{3, 4})).Collect() 12 | 13 | fmt.Println(numbers) 14 | // Output: [1 2 3 4] 15 | } 16 | 17 | func ExampleIterator2_Chain() { 18 | pairs := itx.FromMap(map[string]int{"a": 1}).Chain(maps.All(map[string]int{"b": 2})) 19 | 20 | fmt.Println(len(maps.Collect(pairs.Seq()))) 21 | // Output: 2 22 | } 23 | -------------------------------------------------------------------------------- /it/itx/channel.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // FromChannel yields values from a channel. 6 | // 7 | // In order to avoid a deadlock, the channel must be closed before attempting 8 | // to called `stop` on a pull-style iterator. 9 | func FromChannel[V any](channel <-chan V) Iterator[V] { 10 | return Iterator[V](it.FromChannel(channel)) 11 | } 12 | 13 | // ToChannel is a convenience method for chaining [it.ToChannel] on 14 | // [Iterator]s. 15 | func (iterator Iterator[V]) ToChannel() <-chan V { 16 | return it.ToChannel(iterator) 17 | } 18 | -------------------------------------------------------------------------------- /it/itx/channel_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it/filter" 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleFromChannel() { 11 | items := make(chan int) 12 | 13 | go func() { 14 | defer close(items) 15 | items <- 1 16 | items <- 2 17 | items <- 0 18 | }() 19 | 20 | for number := range itx.FromChannel(items).Exclude(filter.IsZero) { 21 | fmt.Println(number) 22 | } 23 | 24 | // Output: 25 | // 1 26 | // 2 27 | } 28 | 29 | func ExampleIterator_ToChannel() { 30 | channel := itx.FromSlice([]int{1, 2, 3}).ToChannel() 31 | 32 | for number := range channel { 33 | fmt.Println(number) 34 | } 35 | 36 | // Output: 37 | // 1 38 | // 2 39 | // 3 40 | } 41 | -------------------------------------------------------------------------------- /it/itx/cycle.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Cycle is a convenience method for chaining [it.Cycle] on [Iterator]s. 6 | func (iterator Iterator[V]) Cycle() Iterator[V] { 7 | return Iterator[V](it.Cycle(iterator)) 8 | } 9 | 10 | // Cycle is a convenience method for chaining [it.Cycle2] on [Iterator2]s. 11 | func (iterator Iterator2[V, W]) Cycle() Iterator2[V, W] { 12 | return Iterator2[V, W](it.Cycle2(iterator)) 13 | } 14 | -------------------------------------------------------------------------------- /it/itx/cycle_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleIterator_Cycle() { 11 | numbers := itx.FromSlice([]int{1, 2}).Cycle().Take(5).Collect() 12 | 13 | fmt.Println(numbers) 14 | // Output: [1 2 1 2 1] 15 | } 16 | 17 | func ExampleIterator2_Cycle() { 18 | numbers := itx.FromMap(map[int]string{1: "one"}).Cycle().Take(5) 19 | 20 | fmt.Println(maps.Collect(numbers.Seq())) 21 | // Output: map[1:one] 22 | } 23 | -------------------------------------------------------------------------------- /it/itx/drop.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Drop is a convenience method for chaining [it.Drop] on [Iterator]s. 6 | func (iterator Iterator[V]) Drop(count uint) Iterator[V] { 7 | return Iterator[V](it.Drop(iterator, count)) 8 | } 9 | 10 | // Drop is a convenience method for chaining [it.Drop2] on [Iterator2]s. 11 | func (iterator Iterator2[V, W]) Drop(count uint) Iterator2[V, W] { 12 | return Iterator2[V, W](it.Drop2(iterator, count)) 13 | } 14 | 15 | // DropWhile is a convenience method for chaining [it.DropWhile] on 16 | // [Iterator]s. 17 | func (iterator Iterator[V]) DropWhile(predicate func(V) bool) Iterator[V] { 18 | return Iterator[V](it.DropWhile(iterator, predicate)) 19 | } 20 | 21 | // DropWhile is a convenience method for chaining [it.DropWhile2] on 22 | // [Iterator2]s. 23 | func (iterator Iterator2[V, W]) DropWhile(predicate func(V, W) bool) Iterator2[V, W] { 24 | return Iterator2[V, W](it.DropWhile2(iterator, predicate)) 25 | } 26 | -------------------------------------------------------------------------------- /it/itx/drop_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/filter" 8 | "github.com/BooleanCat/go-functional/v2/it/itx" 9 | ) 10 | 11 | func ExampleIterator_Drop() { 12 | for value := range itx.FromSlice([]int{1, 2, 3, 4, 5}).Drop(2) { 13 | fmt.Println(value) 14 | } 15 | 16 | // Output: 17 | // 3 18 | // 4 19 | // 5 20 | } 21 | 22 | func ExampleIterator2_Drop() { 23 | numbers := itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Drop(1) 24 | 25 | fmt.Println(len(maps.Collect(numbers.Seq()))) 26 | // Output: 2 27 | } 28 | 29 | func ExampleIterator_DropWhile() { 30 | for value := range itx.FromSlice([]int{1, 2, 3, 4, 5}).DropWhile(filter.LessThan(3)) { 31 | fmt.Println(value) 32 | } 33 | 34 | // Output: 35 | // 3 36 | // 4 37 | // 5 38 | } 39 | 40 | func ExampleIterator2_DropWhile() { 41 | lessThanThree := func(int, v int) bool { return v < 3 } 42 | 43 | _, numbers := itx.FromSlice([]int{1, 2, 3, 4, 5}).Enumerate().DropWhile(lessThanThree).Collect() 44 | fmt.Println(numbers) 45 | // Output: [3 4 5] 46 | } 47 | -------------------------------------------------------------------------------- /it/itx/enumerate.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Enumerate is a convenience method for chaining [it.Enumerate] on 6 | // [Iterator]s. 7 | func (iterator Iterator[V]) Enumerate() Iterator2[int, V] { 8 | return Iterator2[int, V](it.Enumerate(iterator)) 9 | } 10 | -------------------------------------------------------------------------------- /it/itx/enumerate_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it/itx" 7 | ) 8 | 9 | func ExampleIterator_Enumerate() { 10 | for index, value := range itx.FromSlice([]int{1, 2, 3}).Enumerate() { 11 | fmt.Println(index, value) 12 | } 13 | 14 | // Output: 15 | // 0 1 16 | // 1 2 17 | // 2 3 18 | } 19 | -------------------------------------------------------------------------------- /it/itx/exhausted.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Exhausted is an iterator that yields no values. 6 | func Exhausted[V any]() Iterator[V] { 7 | return Iterator[V](it.Exhausted[V]()) 8 | } 9 | 10 | // Exhausted2 is an iterator that yields no values. 11 | func Exhausted2[V, W any]() Iterator2[V, W] { 12 | return Iterator2[V, W](it.Exhausted2[V, W]()) 13 | } 14 | -------------------------------------------------------------------------------- /it/itx/exhausted_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it/itx" 7 | ) 8 | 9 | func ExampleExhausted() { 10 | fmt.Println(len(itx.Exhausted[int]().Collect())) 11 | // Output: 0 12 | } 13 | 14 | func ExampleExhausted2() { 15 | fmt.Println(len(itx.Exhausted2[int, string]().Left().Collect())) 16 | // Output: 0 17 | } 18 | -------------------------------------------------------------------------------- /it/itx/filter.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Filter is a convenience method for chaining [it.Filter] on [Iterator]s. 6 | func (iterator Iterator[V]) Filter(predicate func(V) bool) Iterator[V] { 7 | return Iterator[V](it.Filter(iterator, predicate)) 8 | } 9 | 10 | // Exclude is a convenience method for chaining [it.Exclude] on [Iterator]s. 11 | func (iterator Iterator[V]) Exclude(predicate func(V) bool) Iterator[V] { 12 | return Iterator[V](it.Exclude(iterator, predicate)) 13 | } 14 | 15 | // Filter is a convenience method for chaining [it.Filter2] on [Iterator2]s. 16 | func (iterator Iterator2[V, W]) Filter(predicate func(V, W) bool) Iterator2[V, W] { 17 | return Iterator2[V, W](it.Filter2(iterator, predicate)) 18 | } 19 | 20 | // Exclude is a convenience method for chaining [it.Exclude2] on [Iterator2]s. 21 | func (iterator Iterator2[V, W]) Exclude(predicate func(V, W) bool) Iterator2[V, W] { 22 | return Iterator2[V, W](it.Exclude2(iterator, predicate)) 23 | } 24 | 25 | // FilterError is a convenience method for chaining [it.FilterError] on 26 | // [Iterator]s. 27 | func (iterator Iterator[V]) FilterError(predicate func(V) (bool, error)) Iterator2[V, error] { 28 | return Iterator2[V, error](it.FilterError(iterator, predicate)) 29 | } 30 | 31 | // ExcludeError is a convenience method for chaining [it.ExcludeError] on 32 | // [Iterator]s. 33 | func (iterator Iterator[V]) ExcludeError(predicate func(V) (bool, error)) Iterator2[V, error] { 34 | return Iterator2[V, error](it.ExcludeError(iterator, predicate)) 35 | } 36 | -------------------------------------------------------------------------------- /it/itx/filter_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it" 7 | "github.com/BooleanCat/go-functional/v2/it/filter" 8 | "github.com/BooleanCat/go-functional/v2/it/itx" 9 | ) 10 | 11 | func ExampleIterator_Filter() { 12 | for number := range itx.FromSlice([]int{1, 2, 3, 4, 5}).Filter(filter.IsEven) { 13 | fmt.Println(number) 14 | } 15 | 16 | // Output: 17 | // 2 18 | // 4 19 | } 20 | 21 | func ExampleIterator_Exclude() { 22 | for number := range itx.FromSlice([]int{1, 2, 3, 4, 5}).Exclude(filter.IsEven) { 23 | fmt.Println(number) 24 | } 25 | 26 | // Output: 27 | // 1 28 | // 3 29 | // 5 30 | } 31 | 32 | func ExampleIterator2_Filter() { 33 | isOne := func(n int, _ string) bool { return n == 1 } 34 | numbers := map[int]string{1: "one", 2: "two", 3: "three"} 35 | 36 | for key, value := range itx.FromMap(numbers).Filter(isOne) { 37 | fmt.Println(key, value) 38 | } 39 | 40 | // Output: 1 one 41 | } 42 | 43 | func ExampleIterator2_Exclude() { 44 | isOne := func(n int, _ string) bool { return n == 1 } 45 | numbers := map[int]string{1: "one", 3: "three"} 46 | 47 | for key, value := range itx.FromMap(numbers).Exclude(isOne) { 48 | fmt.Println(key, value) 49 | } 50 | 51 | // Output: 3 three 52 | } 53 | 54 | func ExampleIterator_FilterError() { 55 | isEven := func(n int) (bool, error) { return n%2 == 0, nil } 56 | 57 | evens, err := it.TryCollect(itx.FromSlice([]int{1, 2, 3, 4, 5}).FilterError(isEven)) 58 | if err == nil { 59 | fmt.Println(evens) 60 | } 61 | 62 | // Output: [2 4] 63 | } 64 | 65 | func ExampleIterator_ExcludeError() { 66 | isEven := func(n int) (bool, error) { return n%2 == 0, nil } 67 | 68 | odds, err := it.TryCollect(itx.FromSlice([]int{1, 2, 3, 4, 5}).ExcludeError(isEven)) 69 | if err == nil { 70 | fmt.Println(odds) 71 | } 72 | 73 | // Output: [1 3 5] 74 | } 75 | -------------------------------------------------------------------------------- /it/itx/integers.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Integers yields all integers in the range [start, stop) with the given step. 6 | func Integers[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64](start, stop, step V) Iterator[V] { 7 | return Iterator[V](it.Integers(start, stop, step)) 8 | } 9 | 10 | // NaturalNumbers yields all non-negative integers in ascending order. 11 | func NaturalNumbers[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64]() Iterator[V] { 12 | return Iterator[V](it.NaturalNumbers[V]()) 13 | } 14 | -------------------------------------------------------------------------------- /it/itx/integers_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it/itx" 7 | ) 8 | 9 | func ExampleNaturalNumbers() { 10 | fmt.Println(itx.NaturalNumbers[int]().Take(4).Collect()) 11 | // Output: [0 1 2 3] 12 | } 13 | 14 | func ExampleIntegers() { 15 | fmt.Println(itx.Integers[uint](0, 3, 1).Collect()) 16 | // Output: [0 1 2] 17 | } 18 | -------------------------------------------------------------------------------- /it/itx/iter.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import ( 4 | "iter" 5 | "maps" 6 | "slices" 7 | 8 | "github.com/BooleanCat/go-functional/v2/it" 9 | ) 10 | 11 | type ( 12 | // Iterator is a wrapper around [iter.Seq] that allows for method chaining of 13 | // most iterators found in the `it` package. 14 | Iterator[V any] func(func(V) bool) 15 | 16 | // Iterator2 is a wrapper around [iter.Seq2] that allows for method chaining 17 | // of most iterators found in the `it` package. 18 | Iterator2[V, W any] func(func(V, W) bool) 19 | ) 20 | 21 | // From converts an iterator in an [Iterator] to support method chaining. 22 | func From[V any](iterator func(func(V) bool)) Iterator[V] { 23 | return Iterator[V](iterator) 24 | } 25 | 26 | // From2 converts an iterator in an [Iterator2] to support method chaining. 27 | func From2[V, W any](iterator func(func(V, W) bool)) Iterator2[V, W] { 28 | return Iterator2[V, W](iterator) 29 | } 30 | 31 | // FromSlice converts a slice to an [Iterator]. 32 | func FromSlice[V any](slice []V) Iterator[V] { 33 | return Iterator[V](slices.Values(slice)) 34 | } 35 | 36 | // FromMap converts a map to an [Iterator2]. 37 | func FromMap[V comparable, W any](m map[V]W) Iterator2[V, W] { 38 | return Iterator2[V, W](maps.All(m)) 39 | } 40 | 41 | // Seq converts an [Iterator] to an [iter.Seq]. 42 | func (iterator Iterator[V]) Seq() iter.Seq[V] { 43 | return iter.Seq[V](iterator) 44 | } 45 | 46 | // Seq converts an [Iterator2] to an [iter.Seq2]. 47 | func (iterator Iterator2[V, W]) Seq() iter.Seq2[V, W] { 48 | return iter.Seq2[V, W](iterator) 49 | } 50 | 51 | // Collect is a convenience method for chaining [slices.Collect] on 52 | // [Iterator]s. 53 | func (iterator Iterator[V]) Collect() []V { 54 | return slices.Collect(iter.Seq[V](iterator)) 55 | } 56 | 57 | // ForEach is a convenience method for chaining [it.ForEach] on [Iterator]s. 58 | func (iterator Iterator[V]) ForEach(fn func(V)) { 59 | it.ForEach(iterator, fn) 60 | } 61 | 62 | // ForEach is a convenience method for chaining [it.ForEach2] on [Iterator2]s. 63 | func (iterator Iterator2[V, W]) ForEach(fn func(V, W)) { 64 | it.ForEach2(iterator, fn) 65 | } 66 | 67 | // Find is a convenience method for chaining [it.Find] on [Iterator]s. 68 | func (iterator Iterator[V]) Find(predicate func(V) bool) (V, bool) { 69 | return it.Find(iterator, predicate) 70 | } 71 | 72 | // Find is a convenience method for chaining [it.Find2] on [Iterator2]s. 73 | func (iterator Iterator2[V, W]) Find(predicate func(V, W) bool) (V, W, bool) { 74 | return it.Find2(iterator, predicate) 75 | } 76 | 77 | // Collect2 consumes an [iter.Seq2] and returns two slices of values. 78 | func (iterator Iterator2[V, W]) Collect() ([]V, []W) { 79 | return it.Collect2(iterator) 80 | } 81 | 82 | // Len is a convenience method for chaining [it.Len] on [Iterator]s. 83 | func (iterator Iterator[V]) Len() int { 84 | return it.Len(iterator) 85 | } 86 | 87 | // Len is a convenience method for chaining [it.Len2] on [Iterator2]s. 88 | func (iterator Iterator2[V, W]) Len() int { 89 | return it.Len2(iterator) 90 | } 91 | 92 | // Drain is a convenience method for chaining [it.Drain] on [Iterator]s. 93 | func (iterator Iterator[V]) Drain() { 94 | it.Drain(iterator) 95 | } 96 | 97 | // Drain2 is a convenience method for chaining [it.Drain2] on [Iterator2]s. 98 | func (iterator Iterator2[V, W]) Drain() { 99 | it.Drain2(iterator) 100 | } 101 | -------------------------------------------------------------------------------- /it/itx/iter_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | 8 | "github.com/BooleanCat/go-functional/v2/it" 9 | "github.com/BooleanCat/go-functional/v2/it/itx" 10 | ) 11 | 12 | func ExampleIterator_Seq() { 13 | fmt.Println(slices.Collect(itx.NaturalNumbers[int]().Take(3).Seq())) 14 | // Output: [0 1 2] 15 | } 16 | 17 | func ExampleIterator2_Seq() { 18 | fmt.Println(maps.Collect(itx.FromMap(map[int]int{1: 2}).Seq())) 19 | // Output: map[1:2] 20 | } 21 | 22 | func ExampleIterator_Collect() { 23 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Collect()) 24 | // Output: [1 2 3] 25 | } 26 | 27 | func ExampleIterator_ForEach() { 28 | itx.FromSlice([]int{1, 2, 3}).ForEach(func(number int) { 29 | fmt.Println(number) 30 | }) 31 | // Output: 32 | // 1 33 | // 2 34 | // 3 35 | } 36 | 37 | func ExampleIterator2_ForEach() { 38 | itx.FromSlice([]int{1, 2, 3}).Enumerate().ForEach(func(index int, number int) { 39 | fmt.Println(index, number) 40 | }) 41 | // Output: 42 | // 0 1 43 | // 1 2 44 | // 2 3 45 | } 46 | 47 | func ExampleIterator_Find() { 48 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Find(func(number int) bool { 49 | return number == 2 50 | })) 51 | // Output: 2 true 52 | } 53 | 54 | func ExampleIterator2_Find() { 55 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Enumerate().Find(func(index int, number int) bool { 56 | return index == 1 57 | })) 58 | // Output: 1 2 true 59 | } 60 | 61 | func ExampleFrom() { 62 | fmt.Println(itx.From(slices.Values([]int{1, 2, 3})).Collect()) 63 | // Output: [1 2 3] 64 | } 65 | 66 | func ExampleFrom2() { 67 | numbers := maps.All(map[int]string{1: "one"}) 68 | fmt.Println(maps.Collect(itx.From2(numbers).Seq())) 69 | // Output: map[1:one] 70 | } 71 | 72 | func ExampleFromSlice() { 73 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Collect()) 74 | // Output: [1 2 3] 75 | } 76 | 77 | func ExampleFromMap() { 78 | fmt.Println(itx.FromMap(map[int]int{1: 2}).Right().Collect()) 79 | // Output: [2] 80 | } 81 | 82 | func ExampleIterator2_Collect() { 83 | indicies, values := itx.FromSlice([]int{1, 2, 3}).Enumerate().Collect() 84 | fmt.Println(values) 85 | fmt.Println(indicies) 86 | 87 | // Output: 88 | // [1 2 3] 89 | // [0 1 2] 90 | } 91 | 92 | func ExampleIterator_Len() { 93 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Len()) 94 | // Output: 3 95 | } 96 | 97 | func ExampleIterator2_Len() { 98 | fmt.Println(itx.FromSlice([]int{1, 2, 3}).Enumerate().Len()) 99 | // Output: 3 100 | } 101 | 102 | func ExampleIterator_Drain() { 103 | itx.From(it.Map(slices.Values([]int{1, 2, 3}), func(n int) int { 104 | fmt.Println(n) 105 | return n 106 | })).Drain() 107 | 108 | // Output: 109 | // 1 110 | // 2 111 | // 3 112 | } 113 | 114 | func ExampleIterator2_Drain() { 115 | itx.From2(it.Map2(slices.All([]int{1, 2, 3}), func(i, n int) (int, int) { 116 | fmt.Println(n) 117 | return i, n 118 | })).Drain() 119 | 120 | // Output: 121 | // 1 122 | // 2 123 | // 3 124 | } 125 | -------------------------------------------------------------------------------- /it/itx/lines.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it" 7 | ) 8 | 9 | // Lines yields lines from an io.Reader. 10 | // 11 | // Note: lines longer than 65536 will cauese an error. 12 | func Lines(r io.Reader) Iterator2[[]byte, error] { 13 | return Iterator2[[]byte, error](it.Lines(r)) 14 | } 15 | 16 | // LinesString yields lines from an io.Reader as strings. 17 | // 18 | // Note: lines longer than 65536 will cauese an error. 19 | func LinesString(r io.Reader) Iterator2[string, error] { 20 | return Iterator2[string, error](it.LinesString(r)) 21 | } 22 | -------------------------------------------------------------------------------- /it/itx/lines_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/filter" 8 | "github.com/BooleanCat/go-functional/v2/it/itx" 9 | ) 10 | 11 | func ExampleLines() { 12 | for line := range itx.Lines(strings.NewReader("one\ntwo\nthree\n")) { 13 | fmt.Println(string(line)) 14 | } 15 | // Output: 16 | // one 17 | // two 18 | // three 19 | } 20 | 21 | func ExampleLinesString() { 22 | reader := strings.NewReader("one\ntwo\n\nthree\n") 23 | 24 | fmt.Println(itx.LinesString(reader).Left().Exclude(filter.IsZero[string]).Collect()) 25 | // Output: [one two three] 26 | } 27 | -------------------------------------------------------------------------------- /it/itx/map.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Transform is a convenience method for chaining [it.Map] on [Iterator]s where 6 | // the provided functions argument type is the same as its return type. 7 | // 8 | // This is a limited version of [it.Map] due to a limitation on Go's type 9 | // system whereby new generic type parameters cannot be defined on methods. 10 | func (iterator Iterator[V]) Transform(f func(V) V) Iterator[V] { 11 | return Iterator[V](it.Map(iterator, f)) 12 | } 13 | 14 | // Transform is a convenience method for chaining [it.Map2] on [Iterator2]s 15 | // where the provided functions argument type is the same as its return type. 16 | // 17 | // This is a limited version of [it.Map2] due to a limitation on Go's type 18 | // system whereby new generic type parameters cannot be defined on methods. 19 | func (iterator Iterator2[V, W]) Transform(f func(V, W) (V, W)) Iterator2[V, W] { 20 | return Iterator2[V, W](it.Map2(iterator, f)) 21 | } 22 | 23 | // TransformError is a convenience method for chaining [it.MapError] on 24 | // [Iterator]s where the provided functions argument type is the same as its 25 | // return type. 26 | // 27 | // This is a limited version of [it.MapError] due to a limitation on Go's type 28 | // system whereby new generic type parameters cannot be defined on methods. 29 | func (iterator Iterator[V]) TransformError(f func(V) (V, error)) Iterator2[V, error] { 30 | return Iterator2[V, error](it.MapError(iterator, f)) 31 | } 32 | -------------------------------------------------------------------------------- /it/itx/map_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it" 8 | "github.com/BooleanCat/go-functional/v2/it/itx" 9 | ) 10 | 11 | func ExampleIterator_Transform() { 12 | fmt.Println(itx.FromSlice([]int{0, 1, 2}).Transform(func(v int) int { 13 | return v + 1 14 | }).Collect()) 15 | // Output: [1 2 3] 16 | } 17 | 18 | func ExampleIterator2_Transform() { 19 | addOne := func(a, b int) (int, int) { 20 | return a + 1, b + 1 21 | } 22 | 23 | fmt.Println(maps.Collect(itx.FromMap(map[int]int{1: 2}).Transform(addOne).Seq())) 24 | // Output: map[2:3] 25 | } 26 | 27 | func ExampleIterator_TransformError() { 28 | fmt.Println(it.TryCollect(itx.FromSlice([]int{0, 1, 2}).TransformError(func(v int) (int, error) { 29 | return v + 1, nil 30 | }))) 31 | // Output: [1 2 3] 32 | } 33 | -------------------------------------------------------------------------------- /it/itx/once.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Once yields the provided value once. 6 | func Once[V any](value V) Iterator[V] { 7 | return Iterator[V](it.Once(value)) 8 | } 9 | 10 | // Once2 yields the provided value pair once. 11 | func Once2[V, W any](v V, w W) Iterator2[V, W] { 12 | return Iterator2[V, W](it.Once2(v, w)) 13 | } 14 | -------------------------------------------------------------------------------- /it/itx/once_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleOnce() { 11 | fmt.Println(itx.Once(42).Chain(itx.Once(43)).Collect()) 12 | // Output: [42 43] 13 | } 14 | 15 | func ExampleOnce2() { 16 | numbers := maps.Collect(itx.Once2(1, 42).Chain(itx.Once2(2, 43)).Seq()) 17 | fmt.Println(numbers[1]) 18 | fmt.Println(numbers[2]) 19 | 20 | // Output: 21 | // 42 22 | // 43 23 | } 24 | -------------------------------------------------------------------------------- /it/itx/repeat.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Repeat yields the same value indefinitely. 6 | func Repeat[V any](value V) Iterator[V] { 7 | return Iterator[V](it.Repeat[V](value)) 8 | } 9 | 10 | // Repeat2 yields the same two values indefinitely. 11 | func Repeat2[V, W any](value1 V, value2 W) Iterator2[V, W] { 12 | return Iterator2[V, W](it.Repeat2(value1, value2)) 13 | } 14 | -------------------------------------------------------------------------------- /it/itx/repeat_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleRepeat() { 11 | fmt.Println(itx.Repeat(1).Take(3).Collect()) 12 | // Output: [1 1 1] 13 | } 14 | 15 | func ExampleRepeat2() { 16 | fmt.Println(maps.Collect(itx.Repeat2(1, 2).Take(5).Seq())) 17 | // Output: map[1:2] 18 | } 19 | -------------------------------------------------------------------------------- /it/itx/take.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Take is a convenience method for chaining [it.Take] on [Iterator]s. 6 | func (iterator Iterator[V]) Take(limit uint) Iterator[V] { 7 | return Iterator[V](it.Take(iterator, limit)) 8 | } 9 | 10 | // Take is a convenience method for chaining [it.Take2] on [Iterator2]s. 11 | func (iterator Iterator2[V, W]) Take(limit uint) Iterator2[V, W] { 12 | return Iterator2[V, W](it.Take2(iterator, limit)) 13 | } 14 | 15 | // TakeWhile is a convenience method for chaining [it.TakeWhile] on 16 | // [Iterator]s. 17 | func (iterator Iterator[V]) TakeWhile(predicate func(V) bool) Iterator[V] { 18 | return Iterator[V](it.TakeWhile(iterator, predicate)) 19 | } 20 | 21 | // TakeWhile is a convenience method for chaining [it.TakeWhile2] on 22 | // [Iterator2]s. 23 | func (iterator Iterator2[V, W]) TakeWhile(predicate func(V, W) bool) Iterator2[V, W] { 24 | return Iterator2[V, W](it.TakeWhile2(iterator, predicate)) 25 | } 26 | -------------------------------------------------------------------------------- /it/itx/take_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/filter" 8 | "github.com/BooleanCat/go-functional/v2/it/itx" 9 | ) 10 | 11 | func ExampleIterator_Take() { 12 | for number := range itx.FromSlice([]int{1, 2, 3, 4, 5}).Take(3) { 13 | fmt.Println(number) 14 | } 15 | 16 | // Output: 17 | // 1 18 | // 2 19 | // 3 20 | } 21 | 22 | func ExampleIterator2_Take() { 23 | numbers := maps.Collect(itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Take(2).Seq()) 24 | 25 | fmt.Println(len(numbers)) 26 | // Output: 2 27 | } 28 | 29 | func ExampleIterator_TakeWhile() { 30 | for number := range itx.FromSlice([]int{1, 2, 3, 4, 5}).TakeWhile(filter.LessThan(4)) { 31 | fmt.Println(number) 32 | } 33 | 34 | // Output: 35 | // 1 36 | // 2 37 | // 3 38 | } 39 | 40 | func ExampleIterator2_TakeWhile() { 41 | lessThanFour := func(int, v int) bool { return v < 4 } 42 | 43 | _, numbers := itx.FromSlice([]int{1, 2, 3, 4, 5}).Enumerate().TakeWhile(lessThanFour).Collect() 44 | fmt.Println(numbers) 45 | // Output: [1 2 3] 46 | } 47 | -------------------------------------------------------------------------------- /it/itx/zip.go: -------------------------------------------------------------------------------- 1 | package itx 2 | 3 | import "github.com/BooleanCat/go-functional/v2/it" 4 | 5 | // Left is a convenience method that unzips an [Iterator2] and returns the left 6 | // iterator, closing the right iterator. 7 | func (iterator Iterator2[V, W]) Left() Iterator[V] { 8 | return Iterator[V](it.Left(iterator)) 9 | } 10 | 11 | // Right is a convenience method that unzips an [Iterator2] and returns the 12 | // right iterator, closing the left iterator. 13 | func (iterator Iterator2[V, W]) Right() Iterator[W] { 14 | return Iterator[W](it.Right(iterator)) 15 | } 16 | -------------------------------------------------------------------------------- /it/itx/zip_test.go: -------------------------------------------------------------------------------- 1 | package itx_test 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it/itx" 8 | ) 9 | 10 | func ExampleIterator2_Left() { 11 | text := strings.NewReader("one\ntwo\nthree\n") 12 | 13 | fmt.Println(itx.LinesString(text).Left().Collect()) 14 | // Output: [one two three] 15 | } 16 | 17 | func ExampleIterator2_Right() { 18 | for value := range itx.FromMap(map[int]string{1: "one"}).Right() { 19 | fmt.Println(value) 20 | } 21 | // Output: one 22 | } 23 | -------------------------------------------------------------------------------- /it/lines.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "iter" 7 | ) 8 | 9 | // Lines yields lines from an io.Reader. 10 | // 11 | // Note: lines longer than 65536 will cause an error. 12 | func Lines(r io.Reader) iter.Seq2[[]byte, error] { 13 | return func(yield func([]byte, error) bool) { 14 | scanner := bufio.NewScanner(r) 15 | for scanner.Scan() { 16 | if !yield(scanner.Bytes(), nil) { 17 | return 18 | } 19 | } 20 | 21 | if err := scanner.Err(); err != nil { 22 | if !yield(nil, err) { 23 | return 24 | } 25 | } 26 | } 27 | } 28 | 29 | // LinesString yields lines from an io.Reader as strings. 30 | // 31 | // Note: lines longer than 65536 will cauese an error. 32 | func LinesString(r io.Reader) iter.Seq2[string, error] { 33 | return Map2(Lines(r), func(b []byte, err error) (string, error) { 34 | return string(b), err 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /it/lines_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/internal/fakes" 11 | "github.com/BooleanCat/go-functional/v2/it" 12 | ) 13 | 14 | func ExampleLines() { 15 | buffer := strings.NewReader("one\ntwo\nthree\n") 16 | for line, err := range it.Lines(buffer) { 17 | if err != nil { 18 | fmt.Println(err) 19 | break 20 | } 21 | 22 | fmt.Println(string(line)) 23 | } 24 | // Output: 25 | // one 26 | // two 27 | // three 28 | } 29 | 30 | func TestLinesError(t *testing.T) { 31 | t.Parallel() 32 | 33 | // Make string 66k bytes long. 34 | var longLine strings.Builder 35 | for i := 0; i < 66*1024; i++ { 36 | longLine.WriteByte('a') 37 | } 38 | 39 | buffer := strings.NewReader(longLine.String()) 40 | for _, err := range it.Lines(buffer) { 41 | assert.True(t, err != nil) 42 | } 43 | } 44 | 45 | func TestLinesYieldsFalse(t *testing.T) { 46 | t.Parallel() 47 | 48 | buffer := strings.NewReader("one\ntwo\nthree\n") 49 | seq := it.Lines(buffer) 50 | 51 | seq(func(l []byte, e error) bool { 52 | return false 53 | }) 54 | } 55 | 56 | func TestLinesYieldsFalseWithError(t *testing.T) { 57 | t.Parallel() 58 | 59 | // Make string 66k bytes long. 60 | var longLine strings.Builder 61 | for i := 0; i < 66*1024; i++ { 62 | longLine.WriteByte('a') 63 | } 64 | 65 | buffer := strings.NewReader(longLine.String()) 66 | seq := it.Lines(buffer) 67 | 68 | seq(func(l []byte, e error) bool { 69 | return false 70 | }) 71 | } 72 | 73 | func TestLinesFailsLater(t *testing.T) { 74 | t.Parallel() 75 | 76 | var ( 77 | count int 78 | lastErr error 79 | ) 80 | 81 | reader := new(fakes.Reader) 82 | reader.ReadReturnsOnCall(0, 1, nil) 83 | reader.ReadReturnsOnCall(1, 0, errors.New("read error")) 84 | 85 | for _, err := range it.LinesString(reader) { 86 | count++ 87 | lastErr = err 88 | } 89 | 90 | assert.Equal(t, lastErr.Error(), "read error") 91 | assert.Equal(t, count, 2) 92 | } 93 | 94 | func ExampleLinesString() { 95 | buffer := strings.NewReader("one\ntwo\nthree\n") 96 | 97 | for line, err := range it.LinesString(buffer) { 98 | if err != nil { 99 | fmt.Println(err) 100 | break 101 | } 102 | 103 | fmt.Println(line) 104 | } 105 | // Output: 106 | // one 107 | // two 108 | // three 109 | } 110 | -------------------------------------------------------------------------------- /it/map.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Map yields values from an iterator that have had the provided function 6 | // applied to each value. 7 | func Map[V, W any](delegate func(func(V) bool), f func(V) W) iter.Seq[W] { 8 | return func(yield func(W) bool) { 9 | for value := range delegate { 10 | if !yield(f(value)) { 11 | return 12 | } 13 | } 14 | } 15 | } 16 | 17 | // Map2 yields pairs of values from an iterator that have had the provided 18 | // function applied to each pair. 19 | func Map2[V, W, X, Y any](delegate func(func(V, W) bool), f func(V, W) (X, Y)) iter.Seq2[X, Y] { 20 | return func(yield func(X, Y) bool) { 21 | for v, w := range delegate { 22 | if !yield(f(v, w)) { 23 | return 24 | } 25 | } 26 | } 27 | } 28 | 29 | // MapError yields values from an iterator that have had the provided function 30 | // applied to each value where the function can return an error. 31 | func MapError[V, W any](delegate func(func(V) bool), f func(V) (W, error)) iter.Seq2[W, error] { 32 | return func(yield func(W, error) bool) { 33 | for value := range delegate { 34 | if !yield(f(value)) { 35 | return 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /it/map_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "maps" 7 | "slices" 8 | "testing" 9 | 10 | "github.com/BooleanCat/go-functional/v2/internal/assert" 11 | "github.com/BooleanCat/go-functional/v2/it" 12 | ) 13 | 14 | func ExampleMap() { 15 | double := func(n int) int { return n * 2 } 16 | 17 | for number := range it.Map(slices.Values([]int{1, 2, 3}), double) { 18 | fmt.Println(number) 19 | } 20 | 21 | // Output: 22 | // 2 23 | // 4 24 | // 6 25 | } 26 | 27 | func TestMapEmpty(t *testing.T) { 28 | t.Parallel() 29 | 30 | assert.Empty[int](t, slices.Collect(it.Map(it.Exhausted[int](), func(int) int { return 0 }))) 31 | } 32 | 33 | func TestMapYieldFalse(t *testing.T) { 34 | t.Parallel() 35 | 36 | numbers := it.Map(slices.Values([]int{1, 2, 3, 4, 5}), func(a int) int { return a + 1 }) 37 | 38 | values := []int{} 39 | numbers(func(v int) bool { 40 | values = append(values, v) 41 | return false 42 | }) 43 | 44 | assert.SliceEqual(t, []int{2}, values) 45 | } 46 | 47 | func ExampleMap2() { 48 | doubleBoth := func(n, m int) (int, int) { 49 | return n * 2, m * 2 50 | } 51 | 52 | pairs := it.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]int{2, 3, 4})) 53 | 54 | for left, right := range it.Map2(pairs, doubleBoth) { 55 | fmt.Println(left, right) 56 | } 57 | 58 | // Output: 59 | // 2 4 60 | // 4 6 61 | // 6 8 62 | } 63 | 64 | func TestMap2Empty(t *testing.T) { 65 | t.Parallel() 66 | 67 | doubleBoth := func(n, m int) (int, int) { return n * 2, m * 2 } 68 | 69 | assert.Equal(t, len(maps.Collect(it.Map2(it.Exhausted2[int, int](), doubleBoth))), 0) 70 | } 71 | 72 | func TestMap2YieldFalse(t *testing.T) { 73 | t.Parallel() 74 | 75 | pairs := slices.All([]int{1, 2, 3}) 76 | 77 | numbers := it.Map2(pairs, func(a, b int) (int, int) { 78 | return a + 1, b + 2 79 | }) 80 | 81 | numbers(func(v, w int) bool { 82 | return false 83 | }) 84 | } 85 | 86 | func ExampleMapError() { 87 | double := func(n int) (int, error) { return n * 2, nil } 88 | 89 | numbers, err := it.TryCollect(it.MapError(slices.Values([]int{1, 2, 3}), double)) 90 | if err == nil { 91 | fmt.Println(numbers) 92 | } 93 | 94 | // Output: [2 4 6] 95 | } 96 | 97 | func TestMapErrorYieldsFalse(t *testing.T) { 98 | t.Parallel() 99 | 100 | numbers := it.MapError(slices.Values([]int{1, 2, 3}), func(a int) (int, error) { 101 | return a + 1, nil 102 | }) 103 | 104 | numbers(func(int, error) bool { 105 | return false 106 | }) 107 | } 108 | 109 | func TestMapErrorError(t *testing.T) { 110 | t.Parallel() 111 | 112 | numbers, errs := it.Collect2(it.MapError(slices.Values([]int{1, 2}), func(a int) (int, error) { 113 | return 0, errors.New("nope") 114 | })) 115 | 116 | assert.SliceEqual(t, []int{0, 0}, numbers) 117 | assert.Equal(t, len(errs), 2) 118 | assert.Equal(t, errs[0].Error(), "nope") 119 | assert.Equal(t, errs[1].Error(), "nope") 120 | } 121 | 122 | func TestMapErrorErrorYieldsFalse(t *testing.T) { 123 | t.Parallel() 124 | 125 | numbers := it.MapError(slices.Values([]int{1, 2}), func(a int) (int, error) { 126 | return 0, errors.New("nope") 127 | }) 128 | 129 | numbers(func(int, error) bool { 130 | return false 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /it/once.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Once yields the provided value once. 6 | func Once[V any](value V) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | if !yield(value) { 9 | return 10 | } 11 | } 12 | } 13 | 14 | // Once2 yields the provided value pair once. 15 | func Once2[V, W any](v V, w W) iter.Seq2[V, W] { 16 | return func(yield func(V, W) bool) { 17 | if !yield(v, w) { 18 | return 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /it/once_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it" 8 | ) 9 | 10 | func ExampleOnce() { 11 | for number := range it.Once(42) { 12 | fmt.Println(number) 13 | } 14 | 15 | // Output: 42 16 | } 17 | 18 | func TestOnceYieldsFalse(t *testing.T) { 19 | t.Parallel() 20 | 21 | once := it.Once(42) 22 | 23 | once(func(int) bool { 24 | return false 25 | }) 26 | } 27 | 28 | func ExampleOnce2() { 29 | for key, value := range it.Once2(1, 2) { 30 | fmt.Println(key, value) 31 | } 32 | 33 | // Output: 1 2 34 | } 35 | 36 | func TestOnce2YieldsFalse(t *testing.T) { 37 | t.Parallel() 38 | 39 | once := it.Once2(1, 2) 40 | 41 | once(func(int, int) bool { 42 | return false 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /it/op/op.go: -------------------------------------------------------------------------------- 1 | package op 2 | 3 | // Add returns the sum of `a` and `b`. 4 | func Add[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~string | ~float32 | ~float64](a, b V) V { 5 | return a + b 6 | } 7 | 8 | // Ref returns a reference to a copy of the provided value. 9 | // 10 | // This may be useful when interacting with packages that use pointers as 11 | // proxies for optional values. 12 | func Ref[V any](v V) *V { 13 | return &v 14 | } 15 | 16 | // Deref returns the value pointed to by the provided pointer. 17 | func Deref[V any](v *V) V { 18 | return *v 19 | } 20 | -------------------------------------------------------------------------------- /it/op/op_test.go: -------------------------------------------------------------------------------- 1 | package op_test 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | 7 | "github.com/BooleanCat/go-functional/v2/it" 8 | "github.com/BooleanCat/go-functional/v2/it/op" 9 | ) 10 | 11 | func ExampleAdd() { 12 | fmt.Println(it.Fold(slices.Values([]int{1, 2, 3}), op.Add, 0)) 13 | // Output: 6 14 | } 15 | 16 | func ExampleAdd_string() { 17 | fmt.Println(it.Fold(slices.Values([]string{"a", "b", "c"}), op.Add, "")) 18 | // Output: abc 19 | } 20 | 21 | func ExampleRef() { 22 | refs := slices.Collect(it.Map(slices.Values([]int{5, 6, 7}), op.Ref)) 23 | fmt.Println(*refs[0], *refs[1], *refs[2]) 24 | // Output: 5 6 7 25 | } 26 | 27 | func ExampleDeref() { 28 | intRef := func(a int) *int { 29 | return &a 30 | } 31 | 32 | values := slices.Values([]*int{intRef(4), intRef(5), intRef(6)}) 33 | 34 | fmt.Println(slices.Collect(it.Map(values, op.Deref))) 35 | // Output: [4 5 6] 36 | } 37 | -------------------------------------------------------------------------------- /it/repeat.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Repeat yields the same value indefinitely. 6 | func Repeat[V any](value V) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for { 9 | if !yield(value) { 10 | return 11 | } 12 | } 13 | } 14 | } 15 | 16 | // Repeat2 yields the same two values indefinitely. 17 | func Repeat2[V, W any](value1 V, value2 W) iter.Seq2[V, W] { 18 | return func(yield func(V, W) bool) { 19 | for { 20 | if !yield(value1, value2) { 21 | return 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /it/repeat_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BooleanCat/go-functional/v2/it" 7 | ) 8 | 9 | func ExampleRepeat() { 10 | for number := range it.Take(it.Repeat(42), 2) { 11 | fmt.Println(number) 12 | } 13 | 14 | // Output: 15 | // 42 16 | // 42 17 | } 18 | 19 | func ExampleRepeat2() { 20 | for v, w := range it.Take2(it.Repeat2(42, "Life"), 2) { 21 | fmt.Println(v, w) 22 | } 23 | 24 | // Output: 25 | // 42 Life 26 | // 42 Life 27 | } 28 | -------------------------------------------------------------------------------- /it/take.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Take yields the first `limit` values from a delegate iterator. 6 | func Take[V any](delegate func(func(V) bool), limit uint) iter.Seq[V] { 7 | return func(yield func(V) bool) { 8 | for value := range delegate { 9 | if limit > 0 { 10 | limit-- 11 | if !yield(value) { 12 | return 13 | } 14 | } else { 15 | return 16 | } 17 | } 18 | } 19 | } 20 | 21 | // Take2 yields the first `limit` pairs of values from a delegate iterator. 22 | func Take2[V, W any](delegate func(func(V, W) bool), limit uint) iter.Seq2[V, W] { 23 | return func(yield func(V, W) bool) { 24 | for v, w := range delegate { 25 | if limit > 0 { 26 | limit-- 27 | if !yield(v, w) { 28 | return 29 | } 30 | } else { 31 | return 32 | } 33 | } 34 | } 35 | } 36 | 37 | // TakeWhile yields values from a delegate iterator until the predicate returns 38 | // false. 39 | func TakeWhile[V any](delegate func(func(V) bool), predicate func(V) bool) iter.Seq[V] { 40 | return func(yield func(V) bool) { 41 | for value := range delegate { 42 | if predicate(value) { 43 | if !yield(value) { 44 | return 45 | } 46 | } else { 47 | return 48 | } 49 | } 50 | } 51 | } 52 | 53 | // TakeWhile2 yields pairs of values from a delegate iterator until the 54 | // predicate returns false. 55 | func TakeWhile2[V, W any](delegate func(func(V, W) bool), predicate func(V, W) bool) iter.Seq2[V, W] { 56 | return func(yield func(V, W) bool) { 57 | for v, w := range delegate { 58 | if predicate(v, w) { 59 | if !yield(v, w) { 60 | return 61 | } 62 | } else { 63 | return 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /it/take_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/it" 11 | "github.com/BooleanCat/go-functional/v2/it/filter" 12 | ) 13 | 14 | func ExampleTake() { 15 | for number := range it.Take(slices.Values([]int{1, 2, 3, 4, 5}), 3) { 16 | fmt.Println(number) 17 | } 18 | 19 | // Output: 20 | // 1 21 | // 2 22 | // 3 23 | } 24 | 25 | func TestTakeZero(t *testing.T) { 26 | t.Parallel() 27 | 28 | assert.Empty[int](t, slices.Collect(it.Take(slices.Values([]int{1, 2, 3}), 0))) 29 | } 30 | 31 | func TestTakeMoreThanAvailable(t *testing.T) { 32 | t.Parallel() 33 | 34 | numbers := slices.Collect(it.Take(slices.Values([]int{1, 2, 3}), 5)) 35 | assert.SliceEqual(t, []int{1, 2, 3}, numbers) 36 | } 37 | 38 | func TestTakeYieldFalse(t *testing.T) { 39 | t.Parallel() 40 | 41 | seq := it.Take(slices.Values([]int{1, 2, 3, 4, 5}), 3) 42 | 43 | seq(func(v int) bool { 44 | return false 45 | }) 46 | } 47 | 48 | func TestTakeEmpty(t *testing.T) { 49 | t.Parallel() 50 | 51 | assert.Empty[int](t, slices.Collect(it.Take(it.Exhausted[int](), 2))) 52 | } 53 | 54 | func ExampleTake2() { 55 | numbers := maps.Collect(it.Take2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 2)) 56 | 57 | fmt.Println(len(numbers)) 58 | // Output: 2 59 | } 60 | 61 | func TestTake2(t *testing.T) { 62 | t.Parallel() 63 | 64 | keys := []int{1, 2, 3} 65 | values := []string{"one", "two", "three"} 66 | 67 | numbers := maps.Collect(it.Take2(it.Zip(slices.Values(keys), slices.Values(values)), 2)) 68 | 69 | assert.Equal(t, len(numbers), 2) 70 | 71 | for key := range numbers { 72 | assert.True(t, slices.Contains(keys, key)) 73 | } 74 | } 75 | 76 | func TestTake2Zero(t *testing.T) { 77 | t.Parallel() 78 | 79 | numbers := maps.Collect(it.Take2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 0)) 80 | assert.Equal(t, len(numbers), 0) 81 | } 82 | 83 | func TestTake2Empty(t *testing.T) { 84 | t.Parallel() 85 | 86 | numbers := maps.Collect(it.Take2(it.Exhausted2[int, int](), 2)) 87 | assert.Equal(t, len(numbers), 0) 88 | } 89 | 90 | func TestTake2MoreThanAvailable(t *testing.T) { 91 | t.Parallel() 92 | 93 | numbers := maps.Collect(it.Take2(maps.All(map[int]string{1: "one", 2: "two"}), 3)) 94 | assert.Equal(t, len(numbers), 2) 95 | } 96 | 97 | func TestTake2YieldFalse(t *testing.T) { 98 | t.Parallel() 99 | 100 | seq := it.Take2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 2) 101 | 102 | seq(func(k int, v string) bool { 103 | return false 104 | }) 105 | } 106 | 107 | func ExampleTakeWhile() { 108 | for number := range it.TakeWhile(slices.Values([]int{1, 2, 3, 4, 5}), filter.LessThan(4)) { 109 | fmt.Println(number) 110 | } 111 | 112 | // Output: 113 | // 1 114 | // 2 115 | // 3 116 | } 117 | 118 | func TestTakeWhileYieldsFalse(t *testing.T) { 119 | t.Parallel() 120 | 121 | seq := it.TakeWhile(slices.Values([]int{1, 2, 3, 4, 5}), filter.LessThan(4)) 122 | 123 | seq(func(n int) bool { 124 | return false 125 | }) 126 | } 127 | 128 | func TestTakeWhileEmpty(t *testing.T) { 129 | t.Parallel() 130 | 131 | assert.Empty[int](t, slices.Collect(it.TakeWhile(it.Exhausted[int](), filter.Passthrough))) 132 | } 133 | 134 | func TestTakeWhileNeverTake(t *testing.T) { 135 | t.Parallel() 136 | 137 | numbers := slices.Collect(it.TakeWhile(slices.Values([]int{1, 2, 3}), func(int) bool { return false })) 138 | assert.Empty[int](t, numbers) 139 | } 140 | 141 | func TestTakeWhileTakeAll(t *testing.T) { 142 | t.Parallel() 143 | 144 | numbers := slices.Collect(it.TakeWhile(slices.Values([]int{1, 2, 3}), filter.Passthrough)) 145 | assert.SliceEqual(t, []int{1, 2, 3}, numbers) 146 | } 147 | 148 | func ExampleTakeWhile2() { 149 | _, values := it.Collect2(it.TakeWhile2(slices.All([]int{1, 2, 3}), func(i int, v int) bool { 150 | return v < 3 151 | })) 152 | 153 | fmt.Println(values) 154 | // Output: [1 2] 155 | } 156 | 157 | func TestTakeWhile2YieldsFalse(t *testing.T) { 158 | t.Parallel() 159 | 160 | seq := it.TakeWhile2(slices.All([]int{1, 2, 3}), func(i int, v int) bool { 161 | return v < 3 162 | }) 163 | 164 | seq(func(i int, v int) bool { 165 | return false 166 | }) 167 | } 168 | 169 | func TestTakeWhile2Empty(t *testing.T) { 170 | t.Parallel() 171 | 172 | _, values := it.Collect2(it.TakeWhile2(it.Exhausted2[int, int](), filter.Passthrough2)) 173 | assert.Empty[int](t, values) 174 | } 175 | 176 | func TestTakeWhile2NeverTake(t *testing.T) { 177 | t.Parallel() 178 | 179 | _, values := it.Collect2(it.TakeWhile2(slices.All([]int{1, 2, 3}), func(int, int) bool { return false })) 180 | assert.Empty[int](t, values) 181 | } 182 | 183 | func TestTakeWhile2TakeAll(t *testing.T) { 184 | t.Parallel() 185 | 186 | _, values := it.Collect2(it.TakeWhile2(slices.All([]int{1, 2, 3}), filter.Passthrough2)) 187 | assert.SliceEqual(t, []int{1, 2, 3}, values) 188 | } 189 | -------------------------------------------------------------------------------- /it/zip.go: -------------------------------------------------------------------------------- 1 | package it 2 | 3 | import "iter" 4 | 5 | // Zip yields pairs of values from two iterators. 6 | func Zip[V, W any](left func(func(V) bool), right func(func(W) bool)) iter.Seq2[V, W] { 7 | return func(yield func(V, W) bool) { 8 | left, stop := iter.Pull(left) 9 | defer stop() 10 | 11 | right, stop := iter.Pull(right) 12 | defer stop() 13 | 14 | for { 15 | leftValue, leftOk := left() 16 | rightValue, rightOk := right() 17 | 18 | if !leftOk || !rightOk { 19 | return 20 | } 21 | 22 | if !yield(leftValue, rightValue) { 23 | return 24 | } 25 | } 26 | } 27 | } 28 | 29 | // Left is a convenience function that unzips an iterator and returns the left 30 | // iterator, closing the right iterator. 31 | func Left[V, W any](delegate func(func(V, W) bool)) iter.Seq[V] { 32 | return func(yield func(V) bool) { 33 | for left := range delegate { 34 | if !yield(left) { 35 | return 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Right is a convenience function that unzips an iterator and returns the 42 | // right iterator, closing the left iterator. 43 | func Right[V, W any](delegate func(func(V, W) bool)) iter.Seq[W] { 44 | return func(yield func(W) bool) { 45 | for _, right := range delegate { 46 | if !yield(right) { 47 | return 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /it/zip_test.go: -------------------------------------------------------------------------------- 1 | package it_test 2 | 3 | import ( 4 | "fmt" 5 | "maps" 6 | "slices" 7 | "testing" 8 | 9 | "github.com/BooleanCat/go-functional/v2/internal/assert" 10 | "github.com/BooleanCat/go-functional/v2/it" 11 | ) 12 | 13 | func ExampleZip() { 14 | numbers := slices.Values([]int{1, 2, 3}) 15 | strings := slices.Values([]string{"one", "two", "three"}) 16 | 17 | for left, right := range it.Zip(numbers, strings) { 18 | fmt.Println(left, right) 19 | } 20 | 21 | // Output: 22 | // 1 one 23 | // 2 two 24 | // 3 three 25 | } 26 | 27 | func TestZipEmpty(t *testing.T) { 28 | t.Parallel() 29 | 30 | assert.Equal(t, len(maps.Collect(it.Zip(it.Exhausted[int](), it.Exhausted[string]()))), 0) 31 | } 32 | 33 | func ExampleLeft() { 34 | for left := range it.Left(maps.All(map[int]string{1: "one"})) { 35 | fmt.Println(left) 36 | } 37 | 38 | // Output: 1 39 | } 40 | 41 | func ExampleRight() { 42 | for right := range it.Right(maps.All(map[int]string{1: "one"})) { 43 | fmt.Println(right) 44 | } 45 | 46 | // Output: one 47 | } 48 | 49 | func TestLeftYieldFalse(t *testing.T) { 50 | t.Parallel() 51 | 52 | numbers := it.Left(maps.All(map[int]string{1: "one"})) 53 | 54 | numbers(func(value int) bool { 55 | return false 56 | }) 57 | } 58 | 59 | func TestRightYieldFalse(t *testing.T) { 60 | t.Parallel() 61 | 62 | strings := it.Right(maps.All(map[int]string{1: "one"})) 63 | 64 | strings(func(value string) bool { 65 | return false 66 | }) 67 | } 68 | --------------------------------------------------------------------------------