creativescala / doodle

Compositional vector graphics in Scala / Scala.JS
https://creativescala.org/doodle/
Apache License 2.0
323 stars 73 forks source link

Unexpected behaviour with `Picture` monad and Java2D renderer #143

Open zainab-ali opened 6 months ago

zainab-ali commented 6 months ago

The behaviour of the picture monad is a bit different to what I would expect. It discards effects when map is used:

Picture.text("mao").drawToIO() // Draws "mao"
Picture.text("mao").map(identity).drawToIO() // Draws nothing

map is implemented in terms of flatMap, which has the same behaviour:

Picture.text("mao").flatMap(_.pure[Picture]).drawToIO() // Draws nothing

On the other hand, functions such as width which construct a new picture still draw:

Picture.text("mao").width.drawToIO() // Draws "mao"

Is this intended?

noelwelsh commented 4 months ago

Hmmm ... I don't think flatMap can be correctly implemented for Picture.

Consider

Picture.circle(10).flatMap(_ => Picture.square(10))

What should this draw? The problem is that there is no natural way to combine the circle and the square. Often it isn't desirable to combine them. Something like

Picture.circle(10).width.flatMap(w => Picture.square(w * 2))

is a typical usage of width and flatMap, where flatMap is only called so the width can be used to create a different element.

So I think the solution is:

zainab-ali commented 3 months ago

Apologies for the delay - I think that's a good approach.

We do still need to define the behaviour of map, andThen and width with respect to drawing.

What do you think about:

Picture.circle(10).width.andThen(w => Picture.square(w * 2)) // Draws the square, but not the circle.
Picture.circle(10).width // Currently draws the circle
Picture.circle(10).map(identity) // === Picture.circle(10) so should draw the circle