Skip to content

Extension properties setters does not work as expected #5588

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

Closed
ibaklan opened this issue Dec 10, 2018 · 6 comments
Closed

Extension properties setters does not work as expected #5588

ibaklan opened this issue Dec 10, 2018 · 6 comments

Comments

@ibaklan
Copy link

ibaklan commented Dec 10, 2018

After recently merged extension methods pull request extension properties definition (getters and setters) become available.
Actually it is possible to define extension setter (together with extension getter), but on use-site only extension getter works as expected, attempt to use extension setter leads to compilation error.

Demonstrating examples (extension activation via implicits or via import - same result)
Example 1 (using import)

object TestMain {
  def main(args: Array[String]): Unit = {
    testExtensionProperty()
  }

  case class Foo(var foo: String)

  object fooExt {
    // define `Foo`-extension property with getter and setter delegating to `Foo.foo`
    def (thisFoo: Foo) fooExt: String = thisFoo.foo
    def (thisFoo: Foo) fooExt_= (value: String): Unit = { thisFoo.foo = value }
  }

  def testExtensionProperty(): Unit = {
    import fooExt._
    val foo = Foo("initVal")
    assert(foo.fooExt == "initVal")
    foo.fooExt = "updatedVal"
    assert(foo.foo == "updatedVal")
    assert(foo.fooExt == "updatedVal")
  }
}

Example 2 (using implicits)

object TestMain2 {
  def main(args: Array[String]): Unit = {
    testExtensionProperty()
  }

  case class Foo(var foo: String)

  implicit object fooExt {
    // define `Foo`-extension property with getter and setter delegating to `Foo.foo`
    def (thisFoo: Foo) fooExt: String = thisFoo.foo
    def (thisFoo: Foo) fooExt_= (value: String): Unit = { thisFoo.foo = value }
  }

  def testExtensionProperty(): Unit = {
    //import fooExt._
    val foo = Foo("initVal")
    assert(foo.fooExt == "initVal")
    foo.fooExt = "updatedVal"
    assert(foo.foo == "updatedVal")
    assert(foo.fooExt == "updatedVal")
  }
}

Expected behaviour - code snippets should be compilable, asserts should be passed successfully

Actual behavior - code compilation fails with error

    foo.fooExt = "updatedVal"
    ^^^^^^^^^^^^^^^^^^^^^^^^^
    reassignment to val `fooExt`

FYI
Kotlin extension properties works as expected (with both getters and setters):
Same (working) example in Kotlin may look like this:

object TestMain {

    @JvmStatic
    fun main(arg: Array<String>) {
        testExtensionProperty()
    }

    data class Foo(var foo: String)

    var Foo.fooExt : String
        get() = foo
        set(value: String) { foo = value }

    fun testExtensionProperty() {
        val foo = Foo("initVal")
        assert(foo.fooExt == "initVal")
        foo.fooExt = "updatedVal"
        assert(foo.foo == "updatedVal")
        assert(foo.fooExt == "updatedVal")
    }
}
@odersky
Copy link
Contributor

odersky commented Dec 16, 2018

According to the current spec, p.x = y is translated to p.x_=(y) if the prefix p has a member x_=. That's not the case for extension methods.

So this would require a spec change, and implementing it would be a considerable complication of the compiler. I am not sure it's worth it, honestly. What's the point to have extension method setters? In particular if you want to encourage a functional style?

@ibaklan
Copy link
Author

ibaklan commented Dec 26, 2018

Well, my thought was that extension method should be as much "indistinguishable" to regular methods as it ever possible. At least this idea looks quite natural for me.
This particular issue was trigger by this post (and following discussion) - that just reminds me to check whether extension setters works or not.

But in general I think that all that:

  • property setters(name_=), apply, update,
  • transparent access to implicit members of this (extension target),
  • access with or without this. prefix

- all that features of regular member methods could be "expect-able" for extension methods.
If all of those before mentioned features would be available with extension methods as well then it could be said that there are no gaps remain in extension methods specification.

Regarding this particular case (extension property with setters), one may admit, probably there are not so much use case:

  1. define custom strictly typed properties on some "HasMap"-like object
    (like foo.barExt <==> foo.map(BAR_KEY):BarType)
  2. defining transformed properties
    like foo.barExt <==> Transformation(foo.bar)
  3. extension property could be some alias to long path, like foo.barBaxExt == foo.bar.baz

No other use cases I could imagine for now. More interesting could be use case with indirection (highlighted later)

But overall, I would say that major motivation for supporting extension properties could be "political".
Comparing Scala and Kotlin extensions methods (and other "extensions related stuff") one may come to conclusion that Kotlin's implementation has less gaps.

In this case willing to close/fill that gaps in Scala extensions "stuff" implementation may send clearly good signal about Scala bright future (for all who may perform that Scala vs Kotlin comparision).

And vise versa - willing to not fill that gabs may send bad signal for all who consider different language alternatives (sometimes people may make their decisions basing on minor things treating them as signs of overall altitude).

From this perspective filling even minor gaps may have good effect (especially when that gaps are already filled in competitors implementation). However this is only my personal feelings - it might be not objective (or wrong).

@ibaklan
Copy link
Author

ibaklan commented Dec 26, 2018

From the other perspective Kotlin's extensions-related stuff is much close to C++'s Pointers to members
With the only difference in how does access to them look like

  • in C++ it looks like targetObj.*memberPointer()
  • while in Kotlin it will be just targetObj.memberPointer() assuming definitions like
    val targetObj: Foo = ... and var memberPointer: Foo.() -> Bar = ...

But what is also missing in Kotlin - is something similar to C++'s pointer to fields (not only to methods).

This feature (I guess) could be even more useful then regular extensions properties (effectively it should be closer to extensions functions type, but those "extensions assignable functions (properties?) types" should be something like regular extension functions types, also equipped with update implementation along with apply one).

Although (any way) it is quite separate feature.

@ibaklan
Copy link
Author

ibaklan commented Dec 26, 2018

FYI: comparison of Kotlin's extensions related features vs Scala's extensions related features could be summarized in following table:

Feature Kotlin Scala
extension methods
members and local
yes
doc
(both members and locals are supported)
yes
(both members and locals are supported)
extension function types
(vals / vars which defines indirect extension methods)
yes
doc , doc
no
there is no way to define
val barExt: SomeExtType[Foo] = ... and then use it immediately as foo.barExt()
extension lambdas
(lambdas with "overloaded" meaning of this)
yes
doc, post (this in extension lambda body points to first parameter, since this. prefix is optional it leads to type safe scope injection)
???
implicit functions lambdas could be used, but scope injection occurs not into regular scope but into implicits scope
discussion1, discussion2 ...
type safe dsl definition yes
doc: Type-Safe Builders - build on top of "extension lambdas" / "extension function types"
yes
but with some limitations:
(*) implementation based on implicit functions types suffers "global scope pollution", which also lead to some issues
(*) possible workaround is too tricky
extension properties yes
doc - both getters and setters supported
yes/no
(only getters are supported)
this. prefix is optional when
accessing extensions applicable to this
yes
this feature heavily used with extension function types
see apply function impl as example: there block is "indirect extension method" (val of ext. function type), block() invocation is in fact resolves to this.block() invocation
no

@Jasper-M
Copy link
Contributor

Jasper-M commented Oct 10, 2019

FWIW this works with scala2-style extension methods, through implicit classes or implicit conversions. I'm not sure why the current spec would allow it to work for implicit conversions but not extension methods?

odersky added a commit to dotty-staging/dotty that referenced this issue Aug 13, 2020
@odersky
Copy link
Contributor

odersky commented Aug 13, 2020

Fixed by #9552

@odersky odersky closed this as completed Aug 13, 2020
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Aug 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants