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

Simplify FunctionK.lift macro #3402

Merged
merged 4 commits into from
Dec 6, 2020
Merged

Conversation

joroKr21
Copy link
Member

Add FunctionK.liftFunction that uses an abstract type member
to emulate a polymorphic function type.

Because liftFunction has worse type inference,
keep the macro based lift that now delegates to liftFunction,
instead of creating a new FunctionK instance every time.
Also accept eta-expanded functions in the macro implementation.

Fixes #3400

@codecov-io
Copy link

codecov-io commented Apr 25, 2020

Codecov Report

Merging #3402 into master will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3402   +/-   ##
=======================================
  Coverage   92.70%   92.71%           
=======================================
  Files         379      379           
  Lines        7981     7984    +3     
  Branches      230      216   -14     
=======================================
+ Hits         7399     7402    +3     
  Misses        582      582           
Flag Coverage Δ
#scala_version_212 92.74% <100.00%> (+<0.01%) ⬆️
#scala_version_213 92.49% <100.00%> (+<0.01%) ⬆️
Impacted Files Coverage Δ
core/src/main/scala/cats/arrow/FunctionK.scala 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8cb9df6...7d93e4d. Read the comment docs.

* Note: The weird `τ[F, G]` parameter is there to compensate for
* the lack of polymorphic function types in Scala 2.
*/
def liftFunction[F[_], G[_]](f: F[τ[F, G]] => G[τ[F, G]]): FunctionK[F, G] = new FunctionK[F, G] {
Copy link
Member

Choose a reason for hiding this comment

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

Are we entirely sure this is sound? I know that similar tricks involving a *-kinded tau are not, but I find it difficult to reason about scalac's behavior in these cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

I took it from the discussion in #2553. We could also call it unsafeLift but I guess it would have to be public because it's called in the expanded macro.

Copy link
Member Author

@joroKr21 joroKr21 Jun 7, 2020

Choose a reason for hiding this comment

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

AFAIU because F and G are type parameters of τ you cannot reuse it with nested FunctionKs which would be a way to exploit unsoundness if we had a bare τ without type parameters.

@joroKr21 joroKr21 force-pushed the functionk-lift branch 2 times, most recently from bc1b7b3 to c204ae4 Compare July 21, 2020 06:11
@codecov-commenter
Copy link

codecov-commenter commented Jul 21, 2020

Codecov Report

Merging #3402 into master will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##           master    #3402   +/-   ##
=======================================
  Coverage   91.30%   91.30%           
=======================================
  Files         386      386           
  Lines        8565     8565           
  Branches      255      263    +8     
=======================================
  Hits         7820     7820           
  Misses        745      745           


test("lift a function directly") {
def headOption[A](list: List[A]): Option[A] = list.headOption
val fHeadOption = FunctionK.liftFunction[List, Option](headOption)
Copy link
Contributor

Choose a reason for hiding this comment

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

A question for my understanding, could this be

Suggested change
val fHeadOption = FunctionK.liftFunction[List, Option](headOption)
val fHeadOption = FunctionK.lift[List, Option](headOption)

Isn't it forwarding to liftFunction anyway ? Or it's testing the public liftFunction explicitly on purpose ?
Apologies if this doesn't make sense :).

Copy link
Member Author

Choose a reason for hiding this comment

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

It's testing liftFunction explicitly. Do you think we should also try to add a dotty version? It has native polymorphic functions, but we don't provide a way to convert them to FunctionK and I think type inference is still not great.

Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense, thanks for clarifying. About the dotty version, I guess it's related to #2553 that's still an open issue for what I understand and specifically there is work going on as note here #2553 (comment). We could sync a dotty specific version with that effort.
So what do you thing (you and others) about letting this PR move on as it improves the current situation described here #3400 and might make some user's life easier ? My approval goes in that direction. Of course I happy to do otherwise if the general preference is different.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I agree this can be merged and I can try to add a dotty version in another PR 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Brilliant, thank 🙇

barambani
barambani previously approved these changes Jul 25, 2020
LukaJCB
LukaJCB previously approved these changes Jul 28, 2020
Copy link
Member

@LukaJCB LukaJCB left a comment

Choose a reason for hiding this comment

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

Thanks for this. Hopefully we can find a dotty solution soon as well

@djspiewak
Copy link
Member

Thanks for this. Hopefully we can find a dotty solution soon as well

FYI I actually have a Dotty solution already, but it doesn't compile because higher-rank type inference isn't fully implemented. @smarter has mentioned that this is something he wants to do, but he hasn't had the time.

@joroKr21
Copy link
Member Author

FYI I actually have a Dotty solution already, but it doesn't compile because higher-rank type inference isn't fully implemented.

Yes, that's what I meant, type inference doesn't work. I'm not sure if this can be solved with a macro as in Scala 2 but we can at least have lift for Dotty that takes a polymorphic function.

@barambani
Copy link
Contributor

@djspiewak , sorry for the ping, this PR has two approvals so if you're happy with the changes and the conflicts are resolved I would merge it in.

Add `FunctionK.liftFunction` that uses an abstract type member
to emulate a polymorphic function type.

Because `liftFunction` has worse type inference,
keep the macro based `lift` that now delegates to `liftFunction`,
instead of creating a new `FunctionK` instance every time.
Also accept eta-expanded functions in the macro implementation.
barambani
barambani previously approved these changes Aug 6, 2020
Copy link
Contributor

@barambani barambani left a comment

Choose a reason for hiding this comment

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

👍

@larsrh larsrh merged commit 8e8be51 into typelevel:master Dec 6, 2020
@joroKr21 joroKr21 deleted the functionk-lift branch December 6, 2020 10:31
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

Successfully merging this pull request may close these issues.

Macro Error in FunctionK.lift
7 participants