scalafx icon indicating copy to clipboard operation
scalafx copied to clipboard

documentation: First example doesn't compile

Open mberndt123 opened this issue 4 years ago • 15 comments

Hi,

I was trying to get started with ScalaFX and wanted to get the very first example in the documentation working. This one:

import scalafx.Includes._
import scalafx.application.JFXApp3
import scalafx.scene.Scene
import scalafx.scene.paint.Color._
import scalafx.scene.shape.Rectangle

object HelloStageDemo extends JFXApp3 {

  override def start(): Unit = {
    stage = new JFXApp3.PrimaryStage {
      title.value = "Hello Stage"
      width = 600
      height = 450
      scene = new Scene {
        fill = LightGreen
        content = new Rectangle {
          x = 25
          y = 40
          width = 100
          height = 100
          fill <== when(hover) choose Green otherwise Red
        }
      }
    }
  }
}

I'm using Scala 3.1.0, ScalaFX 17.0.1-R26, JavaFX 17.

This is the error:

None of the overloaded alternatives of method <== in trait Property with types
 (v: 
  scalafx.beans.value.ObservableValue[? <: javafx.scene.paint.Paint, ?
     <: javafx.scene.paint.Paint
  ]
): Unit
 (v: javafx.beans.value.ObservableValue[? <: javafx.scene.paint.Paint]): Unit
match arguments (scalafx.beans.binding.ObjectBinding[J])

It seems to be a problem with type inference or implicit conversions, because I was able to fix it by specifying the type parameter for choose explicitly:

fill <== when(hover).choose[javafx.scene.paint.Paint] (Green) otherwise (Red)

mberndt123 avatar Jan 27 '22 17:01 mberndt123

Yes there is a Scala 3 issue that breaks the example. One of the workaround is as you showed. This one of the only few major issues with Scala 3. Unfortunately it impacts the first example.

That issue was observed in prerelease versions of Scala 3. I was hoping that it will be fixed, but it is there. There best will be to report it to the Scala team.

We will also need to change the first example to something that currently cleanly works with Scala 3. Any suggestions for a nice example?

jpsacha avatar Jan 30 '22 19:01 jpsacha

Hey @jpsacha,

I suspect that this isn't just a Scala compiler bug that is going to be fixed. The type inference algorithm just works differently now, often better and sometimes worse. I don't think the old behaviour is going to come back, and ScalaFX may need to change in order to offer good developer ergonomics going forward. I'm afraid this is not just a documentation issue.

Regarding the example in the documentation, I would fix it by simply importing javafx.scene.paint.Color._ instead of scalafx.scene.paint.Color._ and replacing LightGreen, Green and Red by LIGHTGREEN, GREEN and RED, respectively.

Frankly, I don't understand the purpose of ScalaFX's Color (or Paint) type. Why not just use JavaFX's Color type? It's nice to have constants that follow the typical Scala naming conventions (Red, not RED), but a custom Color type is not needed for that.

mberndt123 avatar Feb 03 '22 17:02 mberndt123

Suggestion how to address the with the new type inference operation is very welcome.

The intention is, for clear code, to only use ScalaFX types. In this example, in Scala 3, this ability is broken.

jpsacha avatar Feb 04 '22 01:02 jpsacha

Well, the issue is this definition in Shape.scala:

def fill: ObjectProperty[jfxsp.Paint]

It’s defined as a JavaFX Paint property. If we want it to work with ScalaFX’s Paint type, then the definition of the property needs to change to use that type. But that would be an incompatible change.

The only other solution to make the example compile (without explicit type parameters, that is) is to use the JavaFX colour constants.

So I think you need to make a choice here. Which one is more important: backward compatibility or the ability to use only ScalaFX types?

mberndt123 avatar Feb 04 '22 01:02 mberndt123

Any suggestions for a nice example?

The shortest non-trivial example, at 24 lines of code, is the PieChartDemo. The DatePicker demo is also short (65 lines) and impressive. The ColorSelector is actually a useful tool, but at 283 lines might not be the best First Example.

(line counts are not counting the copyright message at the top).

philwalk avatar May 25 '24 20:05 philwalk

Here's a scala3 compatible version of the current First Example. The only change was to alter the Color import statement.

import scalafx.Includes.*
import scalafx.application.JFXApp3
import scalafx.scene.Scene
import javafx.scene.paint.Color.{GREEN => Green, RED => Red, LIGHTGREEN => LightGreen}
import scalafx.scene.shape.Rectangle

object HelloStageDemo extends JFXApp3 {

  override def start(): Unit = {
    stage = new JFXApp3.PrimaryStage {
      title.value = "Hello Stage"
      width = 600
      height = 450
      scene = new Scene {
        fill = LightGreen
        content = new Rectangle {
          x = 25
          y = 40
          width = 100
          height = 100
          fill <== when(hover) choose Green otherwise Red
        }
      }
    }
  }
}

philwalk avatar May 27 '24 15:05 philwalk

Someone asked the question ScalaFX type mismatch witch JavaFX namespace in stackoverflow where it details the same problem reported here. I first found the solution by myself and later I found this open issue which was created long time ago. Maybe adding some notes to the docs about this problem with scala 3 could help in the future

gastonschabas avatar Jun 26 '24 05:06 gastonschabas

Someone asked the question ScalaFX type mismatch witch JavaFX namespace in stackoverflow where it details the same problem reported here. I first found the solution by myself and later I found this open issue which was created long time ago. Maybe adding some notes to the docs about this problem with scala 3 could help in the future

@gastonschabas I probably copied the fix from your stackoverflow entry, I should have have credited you. Thanks for sharing your info!

philwalk avatar Jun 26 '24 21:06 philwalk

@philwalk thanks, but I copied your solution. Which means, I have to credit you. Your comment here was written on May 27 and mine on June 23. The sequence was

  1. fix it in my local and post the solution suggested in the same question posted here
  2. found this open issue
  3. edit my answer with your suggestion
  4. edit my answer again crediting you for the comment with this other approach

You might copy the solution from someone else haha

gastonschabas avatar Jun 26 '24 21:06 gastonschabas

What about this example, following @philwalk suggestion, in Scala 3:

import scalafx.application.JFXApp3
import scalafx.application.JFXApp3.PrimaryStage
import scalafx.collections.ObservableBuffer
import scalafx.scene.Scene
import scalafx.scene.chart.PieChart

object PieChartDemo extends JFXApp3:

  private val dataPairs = Seq(("A", 33), ("B", 17), ("C", 11))

  override def start(): Unit =
    stage = new PrimaryStage:
      title = "Pie Chart Demo"
      scene = new Scene:
        root = new PieChart:
          title = "Pie Chart"
          clockwise = false
          startAngle = 7
          data = ObservableBuffer.from(dataPairs.map(PieChart.Data(_, _)))

jpsacha avatar Jun 28 '24 23:06 jpsacha

As for the original example that does not compile in Scala 3. I think the simplest workaround is to explicitly use delegate (underlaying JavaFX type):

fill <== when (hover) choose Green otherwise Red.delegate

This does not require any additional imports or aliases. All imports can remain only from ScalaFX.

jpsacha avatar Jun 29 '24 00:06 jpsacha

@jpsacha, it's not clear to me why we need to reference .delegate for Red but not for Green.

fill <== when (hover) choose Green otherwise Red.delegate

Whatever the explanation, it seems likely to present scalafx as being rather subtle, unless an adjacent comment clarifies it. This also works and seems less irregular:

fill <== when (hover) choose Green.delegate otherwise Red.delegate

philwalk avatar Jun 29 '24 16:06 philwalk

@philwalk I agree that adding .delegate to both may look more "regular". That was actually may original suggestion before the edit. What I am trying to say that adding the hint inside otherwise is sufficient for Scala 3 to infer the types right and compile the code (it is minimal modification that make the code work). Maybe this also a how to fix Scala 3 implementation of when/choose/otherwise to make it work without .delegate or other tricks. I looked into this in the past and could not see the way to make it work, I maybe missing something.

Also note that original workaround for that issue was:

val helper: ObjectBinding[jfxsp.Color] = when(hover) choose Green otherwise Red
fill <== helper

It indicates that Scala 3 can derive the types/apply implicit conversion correctly by a hint on the left-hand-side (as opposed to the .delegate on the right-hand-side). Looks that a lot of the implementation of when/choose/otherwise is working, but there is still something missing.

jpsacha avatar Jun 29 '24 17:06 jpsacha