Translate Haskell Class-based ad-hoc polymorphism example to Scala

Hi all, I had a go at translating the following Haskell

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

to Scala:

  case class Circle(radius: Float)
  case class Rect(width: Float, height: Float)
  
  sealed trait Shape[A]:
    def area(shape: A): Double
  
  object 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.given
  assert(circleShape.area(Circle(1)) == math.Pi)
  assert(rectShape.area(Rect(2,3)) == 6)

When invoking the area function, I am having to specify which of the two versions I mean to invoke, because if I do this:

  import Shape.circleShape._
  import Shape.rectShape._
  assert(area(Circle(1)) == math.Pi)
  assert(area(Rect(2,3)) == 6) 

I get this:

  import Shape.circleShape._
35
  import Shape.rectShape._
36
  assert(area(Circle(1)) == math.Pi)
Reference to area is ambiguous,
it is both imported by import Shape.circleShape._
and imported subsequently by import Shape.rectShape._
Found:    Circle
Required: Rect
37
  assert(area(Rect(2,3)) == 6)  
Reference to area is ambiguous,
it is both imported by import Shape.circleShape._
and imported subsequently by import Shape.rectShape._

Any ideas as to whether trying to achieve the above makes sense and is possible?

Try with:

object Shape: 
  def area[S](shape: S)(using ev: Shape[S]): Double =
    ev.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  
end Shape

assert(Shape.area(Circle(1)) == math.Pi)
4 Likes

Hello @BalmungSan,

that’s pretty cool, thank you for taking the time to come up with it!

When I pressed the lazy ‘ask a friend’ button, I had in the back of my mind that I would have to pass the Shape given instance into any function wishing to invoke area, but it didn’t occur to me that I could do the ‘passing in’ the way you did it, just once, in what is some kind of alias/wrapper of the area function, and is closely associated with Shape.

FWIW, I had a go at using a context bound plus summoning, just to see what it looks like:

    def area[S:Shape](shape: S): Double =
      summon[Shape[S]].area(shape)

Philip

I can also import Shape.area to make things less verbose:

  import Shape.area
  
  assert(area(Circle(1)) == math.Pi)
  assert(area(Rect(2,3)) == 6)  
1 Like

Good news - this works and is pretty close to the original:

case class Circle(radius: Float)
case class Rect(length: Float, width: Float)

trait Shape[A]:
  extension (shape: A)
    def area: Double

given Shape[Circle] with
  extension (c: Circle)
    def area: Double = math.Pi * c.radius * c.radius

given Shape[Rect] with
  extension (r: Rect)
    def area: Double = r.length * r.width

@main def main: Unit =
  assert(Circle(1).area == math.Pi)
  assert(Rect(2,3).area == 6)
2 Likes