Search code examples
scalascala-3

Type mismatch error when using extensions in Scala 3.3


When compiling the following code:

enum Tree[+A]:
    case Leaf(value: A)
    case Branch(left: Tree[A], right: Tree[A])

    extension (t: Tree[Int]) def firstPositive: Int = t match
        case Leaf(i) => i
        case Branch(l, r) =>
            val lpos = l.firstPositive
            if lpos > 0 then lpos else r.firstPositive

I get these errors:

-- [E008] Not Found Error: -----------------------------------------------------
137 |            if lpos > 0 then lpos else r.firstPositive
    |               ^^^^^^
    |value > is not a member of Tree[Int] => Int, but could be made available as an extension method.
    |
    |One of the following imports might make progress towards fixing the problem:
    |
    |  import math.Ordered.orderingToOrdered
    |  import math.Ordering.Implicits.infixOrderingOps
    |
-- [E007] Type Mismatch Error: -------------------------------------------------
137 |            if lpos > 0 then lpos else r.firstPositive
    |                             ^^^^
    |                             Found:    (lpos : Tree[Int] => Int)
    |                             Required: Int
    |
    | longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: -------------------------------------------------
137 |            if lpos > 0 then lpos else r.firstPositive
    |                                       ^^^^^^^^^^^^^^^
    |                                       Found:    Tree[Int] => Int
    |                                       Required: Int
    |
    | longer explanation available when compiling with `-explain`
3 errors found

It looks like l.firstPositive has type Tree[Int] => Int and not Int. One workaround is to write l.firstPositive(l), but then there's no point in using extensions. What am I doing wrong?

Btw, this code snippet is copied from Functional Programming in Scala Second Edition (page 52), which is a very recent book.


Solution

  • That doesn't seem to be a bug, one of the things that I think might be confusing in Scala3 is the indentation rules, that might be misleading in some cases. I haven't read the book yet, but I have a feeling that either you haven't followed the indentations correctly, or it's some mistake in the book. In your case, you're defining an extension method over a specific implementation of Tree, namely Tree[Int], which should be outside the scope of the Tree enum itself. This works perfectly fine:

    enum Tree[+A]:
      case Leaf(value: A)
      case Branch(left: Tree[A], right: Tree[A])
    
    import Tree.*
    
    // the same indentation as Tree definition, meaning it's outside the scope
    extension (t: Tree[Int]) def firstPositive: Int = 
      t match
        case Leaf(i) => i
        case Branch(l, r) =>
            val lpos = l.firstPositive
            if lpos > 0 then lpos else r.firstPositive
    

    TL;DR

    My assumptions on why this is happening (not sure about it)

    One thing to note here is that when you define the extension method inside the Tree, by the definition of a method, it's first argument (the actual instance) is passed to it implicitly:

    enum Tree[+T]:
       // sum types here
       extension (t: Tree[Int]) def whatever: Int = ???
    

    Would be expanded to something like this (not precisely) when you put the extension inside the scope of Tree:

    def whetever(this: Tree[_])(t: Tree[Int]): Int = ???
    

    That might be the reason why lpos is being detected as a function from Tree[Int] to Int. In fact you can write something like this:

    enum Tree[+A]:
      case Leaf(value: A)
      case Branch(left: Tree[A], right: Tree[A])
    
    
      extension (t: Tree[Int]) def firstPositive: Int = 
        t match
          case Leaf(i) => i
          case Branch(l, r) =>
              // first l is "this", latter one is "t"
              val lpos = l.firstPositive(l)
              if lpos > 0 then lpos else r.firstPositive(r)
    

    If my assumptions are correct, I can't see why defining such thing, which is both a method and an extension method at the same time is not a specific compile-time error.