Hi all, I am having a go at translating the following Haskell into Scala:
data Circle = Circle Float
data Rect = Rect Float Float
class Shape a where
area :: a -> Float
instance Shape Circle where
area (Circle r) = pi * r^2
instance Shape Rect where
area (Rect length' width') = length' * width'
main = do
putStrLn (show (area(Circle 1)))
putStrLn (show (area(Rect 2 3)))
Here is the current result:
case class Circle(radius: Float)
case class Rect(width: Float, height: Float)
sealed trait Shape[A]:
def area(shape: A): Double
object Shape:
def area[S:Shape](shape: S): Double =
summon[Shape[S]].area(shape)
given circleShape: Shape[Circle] with
def area(circle: Circle): Double =
math.Pi * circle.radius * circle.radius
given rectShape: Shape[Rect] with
def area(rect: Rect): Double =
rect.width * rect.height
import Shape.area
assert(area(Circle(1)) == math.Pi)
assert(area(Rect(2,3)) == 6)
Are there any improvements that you can think of that would make it more faithful to the original?
It’s needed for “single abstract method” class instantiation via a lambda. Besides, when choosing between an abstract class and a trait, I always pick the abstract class. It’s more straightforward and thus faster to compile since it maps directly to a Java abstract class, whereas traits do not (though this usually only makes a difference in big mixin composition use cases).
Hah, interesting! I tried with a trait at first and Dotty returned an unhelpful message of the “could not infer parameter type” kind. On a tangential note, I found that Dotty tends to give up with this sort of errors much more easily than Scala 2, and when it does it’s often caused by a completely unrelated issue, which makes it quite difficult to work with. I’ll try to open an issue next time this happens to me.
The problem with your approach is that it works only because you have everything in scope. When you move things to different packages, the implicits won’t be found unless you explicitly import them. At least the compiler sometimes suggests this, but it’s still not ideal:
value area is not a member of lib.Circle, but could be made available as an extension method.
The following import might fix the problem:
import lib.given_Shape_Circle
That’s the reason I put the instances in companion objects in my solution.
Hi @LPTK, right, let me see, I switched from bundling the two givens in an object to just having them on their own because I thought the importing ergonomics were reasonable. I was exploring the expression problem [1], and so I didn’t want to be forced to reopen an object in order to add a new given to it, I wanted additivity, the ability to add a new given in a new file, i.e. without having to touch existing files.
I just had a go at putting each program element in its own package, just to see what that forces me to do in terms of imports, and here is what I am seeing:
package explore.traits
trait Shape[A]:
extension (shape: A)
def area: Double