effect icon indicating copy to clipboard operation
effect copied to clipboard

`Match.tagsTupleExhaustive` has been added

Open KhraksMamtsov opened this issue 9 months ago • 9 comments

Type

  • [ ] Refactor
  • [x] Feature
  • [ ] Bug Fix
  • [ ] Optimization
  • [ ] Documentation Update

Description

Match.tagsTupleExhaustive has been added

import { Either, Match, Option } from "effect"

Match.value([Option.some(1), Either.left(33)]).pipe(
  Match.tagsTupleExhaustive({
    NoneLeft: (none, left) => {}, // (none: None<number>, left: Left<number, never>) => void
    NoneRight: (none, right) => {}, // (none: None<number>, left: Right<number, never>) => void
    SomeLeft: (some, left) => {}, // (none: Some<number>, left: Left<number, never>) => void
    SomeRight: (some, right) => {} // (none: Some<number>, left: Right<number, never>) => void
  })
)

It also preserves rendering of union member in type hints. This is a special sugar for tagged sun-types designed to make it easier to work with a pattern in which the user wants to process all possible combinations of union members in a flat manner.

export const queenBeeState = (swarm: Swarm) => {
  const whiteSurroundedQueenBee = getSurroundedQueenBee(swarm, Side.White);
  const blackSurroundedQueenBee = getSurroundedQueenBee(swarm, Side.Black);

  return Match.value(
    distributive([whiteSurroundedQueenBee, blackSurroundedQueenBee])
  ).pipe(
    Match.when([{ _tag: "Some" }, { _tag: "Some" }], ([white, black]) =>
      QueenBeeState.BothSurrounded({ cells: [white.value, black.value] })
    ),
    Match.when([{ _tag: "None" }, { _tag: "Some" }], ([_white, black]) =>
      QueenBeeState.OneSurrounded({ cell: black.value })
    ),
    Match.when([{ _tag: "Some" }, { _tag: "None" }], ([white, _black]) =>
      QueenBeeState.OneSurrounded({ cell: white.value })
    ),
    Match.when([{ _tag: "None" }, { _tag: "None" }], ([_white, _black]) =>
      QueenBeeState.Free()
    ),
    Match.exhaustive
  );
};

The function is not limited to a pair and can take a tuple of arbitrary length. image

Related

  • Related Issue #
  • Closes #

KhraksMamtsov avatar Mar 31 '25 14:03 KhraksMamtsov

🦋 Changeset detected

Latest commit: 35e0ac3cae0e7697874d66150a5861a5d21f0457

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

This PR includes changesets to release 32 packages
Name Type
effect Minor
@effect/cli 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 Major
@effect/sql-clickhouse Major
@effect/sql-d1 Major
@effect/sql-drizzle Major
@effect/sql-kysely Major
@effect/sql-libsql Major
@effect/sql-mssql Major
@effect/sql-mysql2 Major
@effect/sql-pg Major
@effect/sql-sqlite-bun Major
@effect/sql-sqlite-do 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
@effect/ai Major
@effect/ai-anthropic Major
@effect/ai-openai 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

changeset-bot[bot] avatar Mar 31 '25 14:03 changeset-bot[bot]

But maybe would be better to introduce Tagged.ts module with functions for type Tagged = {_tag: string}? @mikearnaldi what do you think?

KhraksMamtsov avatar Mar 31 '25 19:03 KhraksMamtsov

@mikearnaldi Hi! I'm sorry if I'm distracting you, but could you give me some feedback? What do you think about this feature?

KhraksMamtsov avatar Apr 09 '25 09:04 KhraksMamtsov

On one hand, using multiple concatenated tags as keys seems a bit strange. But I think the fact that tags are strings can be used to achieve more compact code.

KhraksMamtsov avatar Apr 29 '25 09:04 KhraksMamtsov

It feels a little too specific to be included. You can do things like this already which gets pretty close:

import { Either, Match, Option } from "effect"

Match.value([Option.some(1), Either.left(33)]).pipe(
  Match.when([Option.isSome, Either.isLeft], ([some, left]) => {}),
  Match.when([Option.isNone, Either.isLeft], ([none, left]) => {}),
  Match.when([Option.isSome, Either.isRight], ([some, right]) => {}),
  Match.when([Option.isNone, Either.isRight], ([none, right]) => {}),
  Match.orElseAbsurd,
)

tim-smart avatar May 05 '25 22:05 tim-smart

I agree – but still, this option doesn't ensure completeness (Match.exhaustive doesn't work) and is more verbose.

Maybe it makes sense to include a helper like this then? With it, Match.exhaustive will work. I think we could simply call it distibutive and overload it to work with both tuples and structs – what do you think?

https://effect.website/play#335756a0cfb9

import { Either, Match, Option } from "effect"

export type Distributive<
  I extends ReadonlyArray<any>,
  Result extends ReadonlyArray<any> = []
> = I extends readonly [infer T, ...infer Rest extends ReadonlyArray<any>]
  ? [T] extends [never] ? Distributive<Rest, [...Result, T]>
  : T extends infer TMember ? Distributive<Rest, [...Result, TMember]>
  : never
  : Result

export const distributiveTuple = <const T extends ReadonlyArray<any>>(
  value: T
): Distributive<T> => {
  return value as any as Distributive<T>
}

Match.value(
  distributiveTuple([Option.some(1), Either.left(33)]) // <- distributiveTuple
).pipe( 
  Match.when([Option.isSome, Either.isLeft], ([some, left]) => {}),
  Match.when([Option.isNone, Either.isLeft], ([none, left]) => {}),
  Match.when([Option.isSome, Either.isRight], ([some, right]) => {}),
  Match.when([Option.isNone, Either.isRight], ([none, right]) => {}),
  Match.exhaustive
)

KhraksMamtsov avatar May 06 '25 13:05 KhraksMamtsov

Probably what really needs changing then is improving the exhaustiveness checks.

tim-smart avatar May 08 '25 23:05 tim-smart

@tim-smart What do you mean? Should we always consider the input data in a distributive way?

KhraksMamtsov avatar May 09 '25 19:05 KhraksMamtsov

The match exclusion logic should be updated so this is exhaustive by default.

tim-smart avatar May 09 '25 21:05 tim-smart