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

Preserve Array.modify and Array.modifyOption non emptiness #3496

Conversation

vinassefranche
Copy link
Contributor

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Same as #3491 but for modify functions

Related

  • Related Issue #
  • Closes #

Copy link

changeset-bot bot commented Aug 22, 2024

🦋 Changeset detected

Latest commit: da5813e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 31 packages
Name Type
effect Minor
@effect/cli Major
@effect/cluster-browser Major
@effect/cluster-node Major
@effect/cluster-workflow Major
@effect/cluster Major
@effect/experimental Major
@effect/opentelemetry Major
@effect/platform-browser Major
@effect/platform-bun Major
@effect/platform-node-shared Major
@effect/platform-node Major
@effect/platform Major
@effect/printer-ansi Major
@effect/printer Major
@effect/rpc-http Major
@effect/rpc Major
@effect/schema Major
@effect/sql-d1 Major
@effect/sql-drizzle Major
@effect/sql-kysely Major
@effect/sql-mssql Major
@effect/sql-mysql2 Major
@effect/sql-pg Major
@effect/sql-sqlite-bun Major
@effect/sql-sqlite-node Major
@effect/sql-sqlite-react-native Major
@effect/sql-sqlite-wasm Major
@effect/sql Major
@effect/typeclass Major
@effect/vitest Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vinassefranche
Copy link
Contributor Author

I'm looking at the doc generation issue. There seems to be a real issue with the way the parameter function passed to Array.modify is now inferred.

@@ -1097,6 1097,8 @@ export const replaceOption: {
<A, B>(self: Iterable<A>, i: number, b: B): Option<Array<A | B>> => modifyOption(self, i, () => b)
)

type TypeOfIterable<T extends Iterable<any>> = T extends Iterable<infer A> ? A : never
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is how I fixed the inference of a. Tell me if:

  • it's a valid way to fix the issue
  • it's the correct place to put this kind of the type helpers (or if it already exists somewhere. I did not find anything on my own)

@gcanti
Copy link
Contributor

gcanti commented Aug 22, 2024

TypeOfIterable

you can use ReadonlyArray.Infer<S> (also I think this change needs more dtslint tests)

i.e.

/**
 * Apply a function to the element at the specified index, creating a new `Array`,
 * or return a copy of the input if the index is out of bounds.
 *
 * @example
 * import { Array } from "effect"
 *
 * const numbers = [1, 2, 3, 4]
 * const result = Array.modify(numbers, 2, (n) => n * 2)
 * assert.deepStrictEqual(result, [1, 2, 6, 4])
 *
 * @since 2.0.0
 */
export const modify: {
  <A, B, S extends Iterable<A> = Iterable<A>>(
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): (self: S) => ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>
  <A, B, S extends Iterable<A> = Iterable<A>>(
    self: S,
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>
} = dual(
  3,
  <A, B>(self: Iterable<A>, i: number, f: (a: A) => B): Array<A | B> =>
    O.getOrElse(modifyOption(self, i, f), () => Array.from(self))
)

/**
 * Apply a function to the element at the specified index, creating a new `Array`,
 * or return `None` if the index is out of bounds.
 *
 * @example
 * import { Array, Option } from "effect"
 *
 * const numbers = [1, 2, 3, 4]
 * const result = Array.modifyOption(numbers, 2, (n) => n * 2)
 * assert.deepStrictEqual(result, Option.some([1, 2, 6, 4]))
 *
 * const outOfBoundsResult = Array.modifyOption(numbers, 5, (n) => n * 2)
 * assert.deepStrictEqual(outOfBoundsResult, Option.none())
 *
 * @since 2.0.0
 */
export const modifyOption: {
  <A, B, S extends Iterable<A> = Iterable<A>>(
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): (self: S) => Option<ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>>
  <A, B, S extends Iterable<A> = Iterable<A>>(
    self: S,
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): Option<ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>>
} = dual(3, <A, B>(self: Iterable<A>, i: number, f: (a: A) => B): Option<Array<A | B>> => {
  const out = Array.from(self)
  if (isOutOfBound(i, out)) {
    return O.none()
  }
  const next = f(out[i])
  // @ts-expect-error
  out[i] = next
  return O.some(out)
})

tested against

// -------------------------------------------------------------------------------------
// modify
// -------------------------------------------------------------------------------------

// $ExpectType string[]
Array.modify([], 0, (
  _n // $ExpectType never
) => "a")

// $ExpectType (string | number)[]
Array.modify(numbers, 0, (
  _n // $ExpectType number
) => "a")

// $ExpectType [number | "a", ...(number | "a")[]]
Array.modify(nonEmptyNumbers, 0, (
  _n // $ExpectType number
) => "a" as const)

// $ExpectType ("a" | 1 | 2)[]
Array.modify(new Set([1, 2] as const), 0, (
  _n // $ExpectType 1 | 2
) => "a" as const)

// $ExpectType string[]
pipe(
  [],
  Array.modify(0, (
    _n // $ExpectType never
  ) => "a")
)

// $ExpectType (string | number)[]
pipe(
  numbers,
  Array.modify(0, (
    _n // $ExpectType number
  ) => "a")
)

// $ExpectType [number | "a", ...(number | "a")[]]
pipe(
  nonEmptyNumbers,
  Array.modify(0, (
    _n // $ExpectType number
  ) => "a" as const)
)

// $ExpectType ("a" | 1 | 2)[]
pipe(
  new Set([1, 2] as const),
  Array.modify(0, (
    _n // $ExpectType 1 | 2
  ) => "a" as const)
)

// -------------------------------------------------------------------------------------
// modifyOption
// -------------------------------------------------------------------------------------

// $ExpectType Option<string[]>
Array.modifyOption([], 0, (
  _n // $ExpectType never
) => "a")

// $ExpectType Option<(string | number)[]>
Array.modifyOption(numbers, 0, (
  _n // $ExpectType number
) => "a")

// $ExpectType Option<[number | "a", ...(number | "a")[]]>
Array.modifyOption(nonEmptyNumbers, 0, (
  _n // $ExpectType number
) => "a" as const)

// $ExpectType Option<("a" | 1 | 2)[]>
Array.modifyOption(new Set([1, 2] as const), 0, (
  _n // $ExpectType 1 | 2
) => "a" as const)

// $ExpectType Option<string[]>
pipe(
  [],
  Array.modifyOption(0, (
    _n // $ExpectType never
  ) => "a")
)

// $ExpectType Option<(string | number)[]>
pipe(
  numbers,
  Array.modifyOption(0, (
    _n // $ExpectType number
  ) => "a")
)

// $ExpectType Option<[number | "a", ...(number | "a")[]]>
pipe(
  nonEmptyNumbers,
  Array.modifyOption(0, (
    _n // $ExpectType number
  ) => "a" as const)
)

// $ExpectType Option<("a" | 1 | 2)[]>
pipe(
  new Set([1, 2] as const),
  Array.modifyOption(0, (
    _n // $ExpectType 1 | 2
  ) => "a" as const)
)

@vinassefranche
Copy link
Contributor Author

TypeOfIterable

you can use ReadonlyArray.Infer<S> (also I think this change needs more dtslint tests)

i.e.

/**
 * Apply a function to the element at the specified index, creating a new `Array`,
 * or return a copy of the input if the index is out of bounds.
 *
 * @example
 * import { Array } from "effect"
 *
 * const numbers = [1, 2, 3, 4]
 * const result = Array.modify(numbers, 2, (n) => n * 2)
 * assert.deepStrictEqual(result, [1, 2, 6, 4])
 *
 * @since 2.0.0
 */
export const modify: {
  <A, B, S extends Iterable<A> = Iterable<A>>(
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): (self: S) => ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>
  <A, B, S extends Iterable<A> = Iterable<A>>(
    self: S,
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>
} = dual(
  3,
  <A, B>(self: Iterable<A>, i: number, f: (a: A) => B): Array<A | B> =>
    O.getOrElse(modifyOption(self, i, f), () => Array.from(self))
)

/**
 * Apply a function to the element at the specified index, creating a new `Array`,
 * or return `None` if the index is out of bounds.
 *
 * @example
 * import { Array, Option } from "effect"
 *
 * const numbers = [1, 2, 3, 4]
 * const result = Array.modifyOption(numbers, 2, (n) => n * 2)
 * assert.deepStrictEqual(result, Option.some([1, 2, 6, 4]))
 *
 * const outOfBoundsResult = Array.modifyOption(numbers, 5, (n) => n * 2)
 * assert.deepStrictEqual(outOfBoundsResult, Option.none())
 *
 * @since 2.0.0
 */
export const modifyOption: {
  <A, B, S extends Iterable<A> = Iterable<A>>(
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): (self: S) => Option<ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>>
  <A, B, S extends Iterable<A> = Iterable<A>>(
    self: S,
    i: number,
    f: (a: ReadonlyArray.Infer<S>) => B
  ): Option<ReadonlyArray.With<S, ReadonlyArray.Infer<S> | B>>
} = dual(3, <A, B>(self: Iterable<A>, i: number, f: (a: A) => B): Option<Array<A | B>> => {
  const out = Array.from(self)
  if (isOutOfBound(i, out)) {
    return O.none()
  }
  const next = f(out[i])
  // @ts-expect-error
  out[i] = next
  return O.some(out)
})

tested against

// -------------------------------------------------------------------------------------
// modify
// -------------------------------------------------------------------------------------

// $ExpectType string[]
Array.modify([], 0, (
  _n // $ExpectType never
) => "a")

// $ExpectType (string | number)[]
Array.modify(numbers, 0, (
  _n // $ExpectType number
) => "a")

// $ExpectType [number | "a", ...(number | "a")[]]
Array.modify(nonEmptyNumbers, 0, (
  _n // $ExpectType number
) => "a" as const)

// $ExpectType ("a" | 1 | 2)[]
Array.modify(new Set([1, 2] as const), 0, (
  _n // $ExpectType 1 | 2
) => "a" as const)

// $ExpectType string[]
pipe(
  [],
  Array.modify(0, (
    _n // $ExpectType never
  ) => "a")
)

// $ExpectType (string | number)[]
pipe(
  numbers,
  Array.modify(0, (
    _n // $ExpectType number
  ) => "a")
)

// $ExpectType [number | "a", ...(number | "a")[]]
pipe(
  nonEmptyNumbers,
  Array.modify(0, (
    _n // $ExpectType number
  ) => "a" as const)
)

// $ExpectType ("a" | 1 | 2)[]
pipe(
  new Set([1, 2] as const),
  Array.modify(0, (
    _n // $ExpectType 1 | 2
  ) => "a" as const)
)

// -------------------------------------------------------------------------------------
// modifyOption
// -------------------------------------------------------------------------------------

// $ExpectType Option<string[]>
Array.modifyOption([], 0, (
  _n // $ExpectType never
) => "a")

// $ExpectType Option<(string | number)[]>
Array.modifyOption(numbers, 0, (
  _n // $ExpectType number
) => "a")

// $ExpectType Option<[number | "a", ...(number | "a")[]]>
Array.modifyOption(nonEmptyNumbers, 0, (
  _n // $ExpectType number
) => "a" as const)

// $ExpectType Option<("a" | 1 | 2)[]>
Array.modifyOption(new Set([1, 2] as const), 0, (
  _n // $ExpectType 1 | 2
) => "a" as const)

// $ExpectType Option<string[]>
pipe(
  [],
  Array.modifyOption(0, (
    _n // $ExpectType never
  ) => "a")
)

// $ExpectType Option<(string | number)[]>
pipe(
  numbers,
  Array.modifyOption(0, (
    _n // $ExpectType number
  ) => "a")
)

// $ExpectType Option<[number | "a", ...(number | "a")[]]>
pipe(
  nonEmptyNumbers,
  Array.modifyOption(0, (
    _n // $ExpectType number
  ) => "a" as const)
)

// $ExpectType Option<("a" | 1 | 2)[]>
pipe(
  new Set([1, 2] as const),
  Array.modifyOption(0, (
    _n // $ExpectType 1 | 2
  ) => "a" as const)
)

I did the changes. Thanks for the review!

@gcanti
Copy link
Contributor

gcanti commented Aug 22, 2024

I did the changes

not all of them actually, take a closer look at the source code here #3496 (comment)
(namely the data-last variants)

@vinassefranche
Copy link
Contributor Author

I did the changes

not all of them actually, take a closer look at the source code here #3496 (comment) (namely the data-last variants)

@gcanti Why do you think it's needed? As S is only used in the "second" function, there's no need to define the type parameter upfront.
It seems to be working fine like this. Did you find a case where something is wrong?

@vinassefranche
Copy link
Contributor Author

I did the changes

not all of them actually, take a closer look at the source code here #3496 (comment) (namely the data-last variants)

@gcanti Why do you think it's needed? As S is only used in the "second" function, there's no need to define the type parameter upfront. It seems to be working fine like this. Did you find a case where something is wrong?

@gcanti My bad, I did not run the dtslint job. It's annoying that it's not visible in Vscode.

@mikearnaldi
Copy link
Member

It is if you install the eslint plugin

@vinassefranche
Copy link
Contributor Author

It is if you install the eslint plugin

Eslint plugin? You're talking about an extension or a real eslint plugin
I have the official eslint extension installed on vscode but I don't see the dtslint issues in the file. See this screenshot:
image

Am I missing something or are we not discussing about the same thing?

@mikearnaldi
Copy link
Member

Ah no apologies I thought you were referring to an eslint error, no unfortunately dtslint is separate

@gcanti gcanti merged commit ca5320c into Effect-TS:next-minor Aug 22, 2024
11 checks passed
@github-actions github-actions bot mentioned this pull request Aug 22, 2024
@vinassefranche vinassefranche deleted the preserve-array-replace-non-emptiness branch August 22, 2024 12:40
dilame pushed a commit to dilame/effect that referenced this pull request Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants