Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds collect methods for arrays of results and maybes #46

Merged
merged 16 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This document outlines the changes from version to version.

## 2.3.0

- Added `Array<Result>.collect()` and `Array<Maybe>.collect()`
JackSpagnoli marked this conversation as resolved.
Show resolved Hide resolved

## 2.2.0

- Added ESM support
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Table of Contents
+ [tail](#tail)
+ [tryFind](#tryfind)
+ [parseDate](#parsedate)
+ [collectResult](#collectResult)
+ [collectMaybe](#collectMaybe)

### Changes from V1 to V2

Expand Down Expand Up @@ -690,3 +692,49 @@ tryFind(u => u.id === '123abc')(users)
Nothing: () => 'Could not find user with id 123abc' // doesn't run
})
```

#### collectResult

Safely collect values from an array of results. Returns a result.

```js
import {collectResult} from 'pratica'

const all_good = [Ok(1), Ok(2), Ok(3)]
const one_bad = [Ok(1), Err('Some error'), Ok(3)]

collectResult(all_good)
.cata({
Ok: x => expect(x).toEqual([1,2,3]), // true
Err: () => 'no values' // doesn't run
})

collectResult(one_bad)
.cata({
Ok: x => x, // doesn't run
Err: err => expect(err).toEqual('Some error') // true
})
```

#### collectMaybe

Safely collect values from an array of maybes. Returns a maybe.

```js
import {collectMaybe} from 'pratica'

const all_good = [Just(1), Just(2), Just(3)]
const one_bad = [Just(1), Nothing, Just(3)]

collectMaybe(all_good)
.cata({
Just: x => expect(x).toEqual([1,2,3]), // true
Nothing: () => 'no values' // doesn't run
})

collectMaybe(one_bad)
.cata({
Just: x => x, // doesn't run
Nothing: () => 'no values' // true
})
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pratica",
"version": "2.2.0",
"version": "2.3.0",
"description": "Functional Programming for Pragmatists",
"main": "dist/index.cjs",
"module": "dist/index.esm.js",
Expand Down
50 changes: 49 additions & 1 deletion specs/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { head } from '../src/head'
import { last } from '../src/last'
import { tail } from '../src/tail'
import { tryFind } from '../src/tryFind'
import { collectResult, collectMaybe } from '../src/collect'

describe('utililties', () => {

Expand Down Expand Up @@ -210,4 +211,51 @@ describe('utililties', () => {
done()
})

})
it('collectResult: should collect an array of Oks into an Ok with an array of values', done => {
const data = [Ok(5), Ok(2), Ok(3)]

collectResult(data)
.cata({
Ok: x => expect(x).toEqual([5,2,3]),
Err: () => done.fail()
})

done()
})

it('collectResult: should collect an array of Oks and Errs into an Err with an array of errors', done => {
const data = [Ok(5), Err('nope'), Ok(3)]

collectResult(data)
.cata({
Ok: () => done.fail(),
Err: x => expect(x).toEqual(['nope'])
})

done()
})

it('collectMaybe: should collect an array of Justs into a Just with an array of values', done => {
const data = [Just(5), Just(2), Just(3)]

collectMaybe(data)
.cata({
Just: x => expect(x).toEqual([5,2,3]),
Nothing: () => done.fail()
})

done()
})

it('collectMaybe: should return a Nothing if any Maybe is a Nothing', done => {
const data = [Just(5), Nothing, Just(3)]

collectMaybe(data)
.cata({
Just: () => done.fail(),
Nothing: () => done()
})

done()
})
})
33 changes: 33 additions & 0 deletions src/collect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Err, Ok, Result } from "./result"
import { Just, Maybe, Nothing } from "./maybe"

export const collectResult = <O, E>(results: Array<Result<O, E>>): Result<Array<O>, Array<E>> => {
const successes: Array<O> = []
const failures: Array<E> = []

for (const result of results) {
if (result.isOk()) {
successes.push(result.value() as O)
} else {
failures.push(result.value() as E)
}
JackSpagnoli marked this conversation as resolved.
Show resolved Hide resolved
}

if (failures.length > 0) {
return Err(failures)
}
return Ok(successes)
Comment on lines +15 to +18
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an existing collect function that you are modelling this logic off-of? I'm just curious how other libraries are using this

Copy link
Contributor Author

@JackSpagnoli JackSpagnoli Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I was trying pratica as part of another project and wrote this when verifying and translating an array to allow for code that looked like inputs.map(translate).collect().
I thought it might be a nice inclusion here, but I can't say I've done any research into wider implementations.

Copy link
Contributor Author

@JackSpagnoli JackSpagnoli Jun 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some digging, turns out I did something similar in Rust ages ago, which explains why I thought to call it collect. Only difference is Rust returns Err with the first found error, so Vec<Result<T, E>> -> Result<Vec<T>, E>, rather than Vec<Result<T, E>> -> Result<Vec<T>, Vec<E>>

fn main() {
    let bad_values: Vec<Result<u32, &str>> = vec![Ok(1), Err("error"), Ok(3)];
    let bad_collection = bad_values.into_iter().collect::<Result<Vec<_>, _>>();

    assert!(bad_collection.is_err());
    assert_eq!(bad_collection.unwrap_err(), "error");

    let good_values: Vec<Result<u32, &str>> = vec![Ok(1), Ok(2), Ok(3)];
    let good_collection = good_values.into_iter().collect::<Result<Vec<_>, _>>();

    assert!(good_collection.is_ok());
    assert_eq!(good_collection.unwrap(), vec![1, 2, 3]);
}

}

export const collectMaybe = <O>(maybes: Array<Maybe<O>>): Maybe<Array<O>> => {
const values: Array<O> = []

for (const maybe of maybes) {
if (maybe.isNothing()) {
return Nothing
}
values.push(maybe.value() as O)
}

return Just(values)
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './oks'
export * from './parseDate'
export * from './result'
export * from './tail'
export * from './tryFind'
export * from './tryFind'
export * from './collect'
Loading