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

Add FunctionN.liftN, parLiftN #4340

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

kubukoz
Copy link
Member

@kubukoz kubukoz commented Nov 12, 2022

liftN is just like mapN, but it should provide better type inference - if you have 6 effects to put in a tuple, you can make a mistake in one and suddenly the whole tuple no longer has a mapN methods. With liftN, you put the function first, and only then pass the (wrapped) elements.

Consider this:

case class User(name: String, age: Int)

def validateName: ValidatedNel[String, String]
def validateAge: ValidatedNel[String, Int]

User.apply.liftN(validateName, validateAge): ValidatedNel[String, User]

tupledF is the same thing, but when your tuples are already in an effect - this can happen if you're unable to do .tupled (e.g. combining cats-parse's Parser + Parser0 - cc @johnynek) or you're just not in control over how the tuple is produced in the effect. tupledF has been removed from this PR, we can revisit it in the future.

@armanbilge armanbilge added this to the 2.10.0 milestone Nov 12, 2022
@kubukoz
Copy link
Member Author

kubukoz commented Dec 23, 2022

I think I'd like to add the parallel versions too, but I don't want to invest additional time without some buy-in from the maintainers: let's talk about these first :)

@johnynek
Copy link
Contributor

I like the idea.

I don't think I like the tupledF name. I think we should iterate on that a bit.

Maybe mapTuple? I don't know.

@kubukoz
Copy link
Member Author

kubukoz commented Dec 23, 2022

I'm fine with pretty much any names ;)

@armanbilge armanbilge removed this from the 2.10.0 milestone Jun 26, 2023
@armanbilge armanbilge added this to the 2.10.0 milestone Jul 1, 2023
@armanbilge armanbilge modified the milestones: 2.10.0, 2.11.0 Aug 14, 2023
@kubukoz kubukoz changed the title Add FunctionN.applyN, tupledF Add FunctionN.liftN, tupledF Jan 3, 2024
@joroKr21
Copy link
Member

joroKr21 commented Jan 3, 2024

@kubukoz
Copy link
Member Author

kubukoz commented Jan 3, 2024

Related: typelevel/kittens#lift-examples

that's pretty cool, I didn't know about it. Frankly I'd still like to have something like this PR in cats because not everybody uses kittens, plus liftN has slightly better UX because of:

  • no tupling
  • no Applicative[TypeConstructor], you start with the function you want to apply, just like if you were to apply it directly with no effects

@joroKr21
Copy link
Member

joroKr21 commented Jan 3, 2024

Sure, I'm not arguing against this PR - just pointing out one could use kittens in the meantime.
Is there any difference between f.liftN.tupled and f.tupledF - i.e. do we need the extra boilerplate and strange name?

@kubukoz
Copy link
Member Author

kubukoz commented Jan 3, 2024

Is there any difference between f.liftN.tupled and f.tupledF - i.e. do we need the extra boilerplate and strange name?

@joroKr21 f.liftN[F].tupled (which is probably (f.liftN[F] _).tupled without -Xsource:3 in pre-3.x Scalas) is ((F[A], F[B])) => F[C], f.tupledF[F] is F[(A, B)] => C.

That said, tupledF offers less of an improvement over a simple map:

  • (??? : F[(String, Int, Boolean)]).map(fapply3) fails with found : (A, B, C) => T, required: ((String, Int, Boolean)) => ?
  • fapply3.tupledF(??? : F[(String, Int, Boolean)]) fails with found : F[(String, Int, Boolean)], required: ?F[(A, B, C)]

They may both have merit, though: ftuple.map(f) focuses on the inputs and lets you pick a function, whereas f.tupledF(ftuple) focuses on the function, letting you pick the arguments.

I don't have a strong opinion on whether tupledF needs to stay, personally I'm in it mostly for liftN. Perhaps it's best to focus on one thing and let the other one go until we find a more compelling reason to have it.

@kubukoz
Copy link
Member Author

kubukoz commented Jan 4, 2024

Added parLiftN for good measure.

@kubukoz kubukoz changed the title Add FunctionN.liftN, tupledF Add FunctionN.liftN, tupledF, parLiftN Jan 4, 2024
@joroKr21
Copy link
Member

joroKr21 commented Jan 4, 2024

Ah ok, the implementation is Functor[F].map(t)(f.tupled) which is the same as t.map(f.tupled) or f.tupled.liftN. Also related to Functor.lift which has no syntax though. It seems like too much hassle for minor details of syntax.

@TonioGela
Copy link
Member

+1 on this to re-raise attention :)

@kubukoz
Copy link
Member Author

kubukoz commented Feb 27, 2024

Removed tupledF so now we only have liftN and parLiftN - shall we get this merged?

@kubukoz kubukoz changed the title Add FunctionN.liftN, tupledF, parLiftN Add FunctionN.liftN, parLiftN Feb 27, 2024
Copy link
Member

@joroKr21 joroKr21 left a comment

Choose a reason for hiding this comment

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

LGTM

@kubukoz
Copy link
Member Author

kubukoz commented Feb 27, 2024

merging with main apparently broke something, I'll take a look.

@kubukoz
Copy link
Member Author

kubukoz commented Feb 27, 2024

cats.jvm.tests.FutureSuite failed, but I don't think it uses the new functions. Could it be a flaky test?

I forgot to write down the failing hash and the logs are now gone :(

@joroKr21
Copy link
Member

joroKr21 commented Feb 27, 2024

Here is the log: https://github.com/typelevel/cats/actions/runs/8066611292/job/22035078228#step:13:7049

==> X cats.jvm.tests.FutureSuite.Future: coflatMap.coflatten throughMap  3.013s munit.FailException: Failing seed: _syITpm-TcruL5pZb_9QYj5RRgP3qQgh3LgMILjdKuM=
You can reproduce this failure by adding the following override to your suite:

  override val scalaCheckInitialSeed = "_syITpm-TcruL5pZb_9QYj5RRgP3qQgh3LgMILjdKuM="

Exception raised on property evaluation.
> ARG_0: Future(Success(1))
> Exception: java.util.concurrent.TimeoutException: Future timed out after [3 seconds]
    at munit.ScalaCheckSuite.propToTry(ScalaCheckSuite.scala:98)
Caused by: java.util.concurrent.TimeoutException: Future timed out after [3 seconds]
    at scala.concurrent.impl.Promise$DefaultPromise.tryAwait0(Promise.scala:248)
    at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:261)
    at scala.concurrent.Await$.$anonfun$result$1(package.scala:201)
    at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:62)
    at scala.concurrent.Await$.result(package.scala:124)
    at cats.jvm.tests.FutureSuite.cats$jvm$tests$FutureSuite$$$anonfun$eqfa$1(FutureSuite.scala:45)

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

Successfully merging this pull request may close these issues.

None yet

6 participants