typelevel / mouse

A small companion to cats
MIT License
368 stars 66 forks source link

Proposal: `unique` and `option`-alike combinators #518

Open satorg opened 4 days ago

satorg commented 4 days ago

Inspired by Doobie's unique and option queries:

https://github.com/typelevel/doobie/blob/1ad3ad5e195d89becbd4097677f738a0b10aa5df/modules/core/src/main/scala/doobie/util/query.scala#L127-L139

In fact, those two are pretty common use cases: after receiving a collection of items from somewhere, we may want to make sure that there's either exactly 1 or at most 1 items received.

To make them generally available, we could consider adding two combinators to FNested2SyntaxOps:

final class FNested2SyntaxOps[F[_], G[_], A](private val fga: F[G[A]]) extends AnyVal {
  ...
  def uniqueOrRaise[E](e: E)(implicit F: MonadError[F, E], G: Foldable[G]): F[A] = ???
  def optionOrRaise[E](e: E)(implicit F: MonadError[F, E], G: Foldable[G]): F[Option[A]] = ???
}

The proposed names may not be perfect – just got borrowed them from Doobie. However, I'm absolutely open for bikeshedding.

If such combinators make sense, I'll be glad to file a PR.

danicheg commented 3 days ago

I like the idea of these methods, but the proposed name seems a little ambiguous and uncertain. I'm more concerned about 'unique', which in the context of SQL seems fine, but outside of that scope, it could imply that the element is distinct in some way, rather than just that the collection contains exactly one element. I think 'sole' might be a better choice here. So, what do you think about soleOrRaise and soleOrEmptyOrRaise? The latter feels clunky but at least echoes the former. Another option here is to use 'ensure', which comes from the MonadError scope: ensureSole and ensureSoleOrEmpty. These names contradict the typical presence of a boolean predicate in the contract of MonadError methods, but we implicitly encode it in the naming with 'sole' and 'soleOrEmpty'.

benhutchison commented 3 days ago

I would be happy to merge a PR with these additions.

I agree with @danicheg's points re: naming. I like ensureSole and ensureSoleOrEmpty myself.

satorg commented 1 day ago

@danicheg , @benhutchison , thank you for your feedback!

Yes, I agree that we should choose better names – I picked those just to have something to start with.

Regarding the suggested ones. At first I favored the ensureSole* options. But then it appeared to me that those names might not be fully idiomatic, because the original ensure and ensureOr combinators do not modify their inputs. But the combinators we're discussing are supposed to do that. So perhaps, the sole...OrRaise names would be a better fit for them.

Btw, is the sole word better in this context comparing to single? Just wondering, because I usually see something like "single-ton" but not "sole-ton". Just out of curiosity though – I'm totally fine with the "sole" wording anyway.

satorg commented 1 day ago

Another thing that I feel it is important to think through.

Initially, I proposed the Foldable typeclass to use for the sake of generalization, which would be able to do the job for sure.

However, there's another one in Cats – UnorderedFoldable, which is a parent for Foldable. For example, UnorderedFoldable captures hash-based Sets. Therefore I feel that UnorderedFoldable could be a better bet to rely upon for the proposed functionality. Unfortunately, UnorderedFoldable looks a bit under-developed at the moment. It doesn't include methods that would allow to short-circuit calculations. For example, Foldable has foldM, etc, but there's nothing like that in UnorderedFoldable. And I wouldn't like to be forced to go through all the items just to check whether there are more than one available.

Therefore I think that perhaps it would be beneficial to give it a shot and add the missing combinators to UnorderedFoldable first, then come back to this task later once the missing functionality becomes enabled. Or do you think that Foldable is already good enough for that?

Thank you!

benhutchison commented 10 hours ago

@satorg It's clear youve given this a lot of thought. I'll back whichever choice you come to in terms of sole / single, and ensure / ..OrRaise.

Seems depending on UnorderedFoldable would be ideal. If you have the patience and energy to extend that class with foldM, then circle back and add these operators, great! If not, and just go with Foldable, it's still a net gain in my view.