Dotty: Extension properties setters does not work as expected

Created on 10 Dec 2018  路  6Comments  路  Source: lampepfl/dotty

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")
    }
}
language enhancement

Most helpful comment

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?

All 6 comments

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?

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

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.

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

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?

Fixed by #9552

Was this page helpful?
0 / 5 - 0 ratings