Skip to content

Commit

Permalink
ReadonlyArray.groupBy: allow for grouping by symbols, closes #2180 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored and mikearnaldi committed Feb 21, 2024
1 parent 7352203 commit 0edc381
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/light-mangos-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

ReadonlyArray.groupBy: allow for grouping by symbols, closes #2180
12 changes: 10 additions & 2 deletions packages/effect/dtslint/ReadonlyArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ declare const pimitiveNumber: number
declare const pimitiveNumerOrString: string | number
declare const predicateNumbersOrStrings: Predicate.Predicate<number | string>

const symA = Symbol.for("a")
const symB = Symbol.for("b")
const symC = Symbol.for("c")

// -------------------------------------------------------------------------------------
// isEmptyReadonlyArray
// -------------------------------------------------------------------------------------
Expand Down Expand Up @@ -99,14 +103,18 @@ pipe(nonEmptyNumbers, ReadonlyArray.map((n) => n + 1))
// groupBy
// -------------------------------------------------------------------------------------

// baseline
// $ExpectType Record<string, [number, ...number[]]>
ReadonlyArray.groupBy([1, 2, 3], String)

// should not return a struct (Record<'positive' | 'negative', ...>) when using string type literals
// $ExpectType Record<string, [number, ...number[]]>
ReadonlyArray.groupBy([1, 2, 3], (n) => n > 0 ? "positive" as const : "negative" as const)

// $ExpectType Record<symbol, [string, ...string[]]>
ReadonlyArray.groupBy(["a", "b"], Symbol.for)

// $ExpectType Record<symbol, [string, ...string[]]>
ReadonlyArray.groupBy(["a", "b"], (s) => s === "a" ? symA : s === "b" ? symB : symC)

// -------------------------------------------------------------------------------------
// some
// -------------------------------------------------------------------------------------
Expand Down
20 changes: 14 additions & 6 deletions packages/effect/src/ReadonlyArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import * as O from "./Option.js"
import * as Order from "./Order.js"
import type { Predicate, Refinement } from "./Predicate.js"
import { isBoolean } from "./Predicate.js"
import * as RR from "./ReadonlyRecord.js"
import * as ReadonlyRecord from "./ReadonlyRecord.js"
import * as Tuple from "./Tuple.js"
import type { NoInfer } from "./Types.js"

Expand Down Expand Up @@ -128,7 +128,7 @@ export const fromIterable = <A>(collection: Iterable<A>): Array<A> =>
* @category conversions
* @since 2.0.0
*/
export const fromRecord: <K extends string, A>(self: Readonly<Record<K, A>>) => Array<[K, A]> = RR.toEntries
export const fromRecord: <K extends string, A>(self: Readonly<Record<K, A>>) => Array<[K, A]> = ReadonlyRecord.toEntries

/**
* @category conversions
Expand Down Expand Up @@ -1334,10 +1334,18 @@ export const group: <A>(self: NonEmptyReadonlyArray<A>) => NonEmptyArray<NonEmpt
* @since 2.0.0
*/
export const groupBy: {
<A>(f: (a: A) => string): (self: Iterable<A>) => Record<string, NonEmptyArray<A>>
<A>(self: Iterable<A>, f: (a: A) => string): Record<string, NonEmptyArray<A>>
} = dual(2, <A>(self: Iterable<A>, f: (a: A) => string): Record<string, NonEmptyArray<A>> => {
const out: Record<string, NonEmptyArray<A>> = {}
<A, K extends string | symbol>(
f: (a: A) => K
): (self: Iterable<A>) => Record<ReadonlyRecord.ReadonlyRecord.NonLiteralKey<K>, NonEmptyArray<A>>
<A, K extends string | symbol>(
self: Iterable<A>,
f: (a: A) => K
): Record<ReadonlyRecord.ReadonlyRecord.NonLiteralKey<K>, NonEmptyArray<A>>
} = dual(2, <A, K extends string | symbol>(
self: Iterable<A>,
f: (a: A) => K
): Record<ReadonlyRecord.ReadonlyRecord.NonLiteralKey<K>, NonEmptyArray<A>> => {
const out: Record<string | symbol, NonEmptyArray<A>> = {}
for (const a of self) {
const k = f(a)
if (Object.prototype.hasOwnProperty.call(out, k)) {
Expand Down
13 changes: 13 additions & 0 deletions packages/effect/test/ReadonlyArray.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import * as String from "effect/String"
import * as fc from "fast-check"
import { assert, describe, expect, it } from "vitest"

const symA = Symbol.for("a")
const symB = Symbol.for("b")
const symC = Symbol.for("c")

describe("ReadonlyArray", () => {
it("exports", () => {
expect(RA.fromRecord).exist
Expand Down Expand Up @@ -971,6 +975,15 @@ describe("ReadonlyArray", () => {
"6": ["foobar"]
}
)
expect(RA.groupBy(["a", "b"], (s) => s === "a" ? symA : s === "b" ? symB : symC)).toStrictEqual({
[symA]: ["a"],
[symB]: ["b"]
})
expect(RA.groupBy(["a", "b", "c", "d"], (s) => s === "a" ? symA : s === "b" ? symB : symC)).toStrictEqual({
[symA]: ["a"],
[symB]: ["b"],
[symC]: ["c", "d"]
})
})

it("match", () => {
Expand Down

0 comments on commit 0edc381

Please sign in to comment.