Effect-TS / effect

An ecosystem of tools to build robust applications in TypeScript
https://effect.website
MIT License
7.58k stars 240 forks source link

Strict pattern matching #2942

Open samhh opened 5 months ago

samhh commented 5 months ago

What is the problem this feature would solve?

Walls of unneeded lambdas when pattern matching harm readability. For example:

import { Data } from "effect"

type Example = Data.TaggedEnum<{
  A: {},
  B: {},
  C: {},
  // etc
}>

const { $match } = Data.taggedEnum<Example>()

$match({
  A: () => "it is A",
  B: () => "this time it is B",
  C: () => "this is wild, it's C!",
  // etc
})

What is the feature you are proposing to solve the problem?

matchX simply makes each case strict. This has different performance characteristics, however in many cases it's viable and improves readability:

$matchX({
  A: "it is A",
  B: "this time it is B",
  C: "this is wild, it's C!",
  // etc
})

This is particularly relevant when there are potentially dozens of sum members.

What alternatives have you considered?

constant, tolerating the lambdas.

mikearnaldi commented 5 months ago

Given that JS has eager evaluation you generally don't want to compute the alternatives unless they match, this isn't something we would like to add for the time being

samhh commented 5 months ago

Given that JS has eager evaluation you generally don't want to compute the alternatives unless they match, this isn't something we would like to add for the time being

I'm really thinking primarily of the example where rather than expensive computations it's just strings or other primitives. We find in our codebase that this comes up quite a lot in pattern matching, and I perhaps naively wonder if this might be more performant than a redundant anonymous function call.

mikearnaldi commented 5 months ago

Given that JS has eager evaluation you generally don't want to compute the alternatives unless they match, this isn't something we would like to add for the time being

I'm really thinking primarily of the example where rather than expensive computations it's just strings or other primitives. We find in our codebase that this comes up quite a lot in pattern matching, and I perhaps naively wonder if this might be more performant than a redundant anonymous function call.

If that kind of perf is important to you probably you shouldn't even use a matcher in the first place, preventive optimization is usually bad

samhh commented 5 months ago

If that kind of perf is important to you probably you shouldn't even use a matcher in the first place, preventive optimization is usually bad

I agree, but wasn't performance your primary concern?

Maybe there's unease about further complicating the pattern matching API, which'd make sense. Just thought I'd raise this though as, in considering a migration path from the fp-ts ecosystem, we've found ourselves quite liking matchX in sum-types.

mikearnaldi commented 5 months ago

If that kind of perf is important to you probably you shouldn't even use a matcher in the first place, preventive optimization is usually bad

I agree, but wasn't performance your primary concern?

Maybe there's unease about further complicating the pattern matching API, which'd make sense. Just thought I'd raise this though as, in considering a migration path from the fp-ts ecosystem, we've found ourselves quite liking matchX in sum-types.

No, my primary concern was semantics, even in the Effect type we had to make matchers lazy because users expected branches not to be computed

samhh commented 5 months ago

No, my primary concern was semantics, even in the Effect type we had to make matchers lazy because users expected branches not to be computed

Gotcha, that makes holistic sense. Thanks for engaging. 🙂

mikearnaldi commented 5 months ago

Wondering if we could use the same api with an optional parameter though, as long as we don't duplicate functions and we can support it across the types I am not against

aminnairi commented 1 month ago

You could also create an identity function that takes an argument, and return a function that return that argument.

const always = <Value>(value: Value) => () => value;

match({
  A: always("it is A"),
  B: always("this time it is B"),
  C: always("this is wild, it's C!"),
});

Similar to what Lodash or Ramda do.

FedericoBiccheddu commented 1 month ago

You could also create an identity function that takes an argument, and return a function that return that argument. [...]

FYI there is const already.