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

Proposal: unique and option-alike combinators #518

Open
satorg opened this issue Oct 1, 2024 · 11 comments
Open

Proposal: unique and option-alike combinators #518

satorg opened this issue Oct 1, 2024 · 11 comments

Comments

@satorg
Copy link

satorg commented Oct 1, 2024

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
Copy link
Member

danicheg commented Oct 1, 2024

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
Copy link
Member

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
Copy link
Author

satorg commented Oct 3, 2024

@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
Copy link
Author

satorg commented Oct 3, 2024

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
Copy link
Member

@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.

@danicheg
Copy link
Member

danicheg commented Oct 5, 2024

Maintaining naming consistency is crucial within a single abstraction, though it's not that big of a deal, since we don't have a universally agreed-upon language. Regarding the Foldable + MonadThrow algebras, ensureSole sounds pretty good to me, despite what ensure means in the context of the MonadError algebra. I even think that ensureSole should return an Option[A] and be defined on top of one of the Foldable type classes. Additionally, ensureSoleOr could return an F[A] and accept an error E as part of the contract. This is where Mouse comes into play, and it might be the right place for this functionality.

@satorg
Copy link
Author

satorg commented Oct 7, 2024

@danicheg ,

I even think that ensureSole should return an Option[A] and be defined on top of one of the Foldable type classes. Additionally, ensureSoleOr could return an F[A] and accept an error E as part of the contract.

Just to clarify, are you suggesting something like this:

def ensureSole(...implicits...): F[Option[A]]
def ensudeSoleOr[E](e: E)(...implicits...): F[A]

?

In that case, the first method wouldn't allow to tell apart a valid None and invalid "more-than-one" values...

@danicheg
Copy link
Member

danicheg commented Oct 7, 2024

valid None and invalid "more-than-one" values...

What stops us from having ensureEmpty and/or ensureSoleOrEmpty? sole is unambiguous here, meaning 'being single'.

@benhutchison
Copy link
Member

@danicheg the bit that confused me was..

I even think that ensureSole should return an Option[A] and be defined on top of one of the Foldable type classes.

Shouldn't ensureSole (ie unique in doobie) just return an A? Use case is we expect exactly one item, or else its error.

Id expect ensureSoleOrEmpty to be the one returning Option[A], use case is we expect either zero or one item, or else its error.

... is that what you were thinking of?

@danicheg
Copy link
Member

danicheg commented Oct 7, 2024

Shouldn't ensureSole (ie unique in doobie) just return an A? Use case is we expect exactly one item, or else its error.

I was suggesting adding ensureSole directly to the Foldable family of type classes. In this context, we can't have A as the result type. But I'm absolutely fine with adding ensureSole: A to Mouse, requiring the two algebras Foldable and MonadThrow, as was originally suggested.

@benhutchison
Copy link
Member

Ah, got you. I feel ensureSole: A is a closer fit to intent of the original doobie combinator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants