Search code examples
scalaoperatorsimplicit-conversioninfix-notationinfix-operator

Scala - defining own infix operators


Methods taking a single argument can be written as an infix operators in Scal. I.e. adding *(other:C) = foo(this, other) to class C, will allow us to write c1 * c2 instead of foo(c1,c2). But is there a way to define infix operators on existing classes that you cannot modify?

E.g. if I wanted to write c1 + c2 instead of xor(c1,c2), where c1,c2:Array[Byte], I obviously cannot modify the Array-Class.

I found this and tried

implicit class Bytearray(a1:Array[Byte]) extends Anyval {
    def +(a2:Array[Byte]) = xor(a1,a2)
}

But that doesn't seem to work (c1 + c2).

Type mismatch, expected:String, actual:Array[Byte]

I thought that perhaps the issue was my using +, so I exchanged it for xor but c1 xor c2 only lead to

Cannot resolve symbol xor

Any suggestions?

UPDATE

Interesting. I had a class Foo with an object Foo defined below it, containing the implicit class. This lead to the aforementioned errors.

However, deleting the object and instead putting the implicit class into a trait BytearrayHandling and then extending it (class Foo extends BytearrayHandling) seems to work. Why is that?


Solution

  • It should be straight forward with the normal declaration of extension methods:

    implicit class ByteArrayOps(private val a1: Array[Byte]) extends AnyVal {
      def + (a2: Array[Byte]): Array[Byte] = 
        (a1 zip a2).map { case (x, y) => (x ^ y).toByte }
    }
    
    "foo".getBytes + "bar".getBytes  // Array(4, 14, 29)
    

    However be aware that sometimes you will run into this:

    Type mismatch, expected:String, actual: X

    This is because of an implicit conversion kicking in that allows you to + anything by converting it to a String. I have given up trying to understand how to deactivate it. It will finally go in Scala 2.12 if I'm not mistaken.

    As eugener pointed out, this error message may indicate that you haven't actually imported your extension method (implicit conversion). For example:

    object MyStuff {
      implicit class ByteArrayOps(private val a1: Array[Byte]) extends AnyVal {
        def + (a2: Array[Byte]): Array[Byte] = 
          (a1 zip a2).map { case (x, y) => (x ^ y).toByte }
      }
    }
    
    "foo".getBytes + "bar".getBytes  // error
    

    gives:

    <console>:14: error: type mismatch;
     found   : Array[Byte]
     required: String
                  "foo".getBytes + "bar".getBytes
                                         ^
    

    because of this Predef conversion. After you import MyStuff.ByteArrayOps, it works.