It's entirely possible (I've done so myself) with appropriate implicits, though the result is not always as utterly seamless as a language that has been designed from the ground up that way.
For example, suppose you want to treat arrays of integers as vectors and want to be able to add them to each other and to scalars. You have to define the operation yourself--Scala can't guess what +
should mean on an array. (Good thing, too, because *
doesn't usually have the obvious element-by-element meaning on matrices, and it means something else again when it comes to convolution!) You can
class ArraysAdd(a: Array[Int]) {
def +(i: Int) = a.map(_ + i)
def +(b: Array[Int]) = {
if (b.length==a.length) (a,b).zipped.map(_ + _).toArray
else throw new IllegalArgumentException
}
}
class ScalarAddsToArray(i: Int) {
def +(a: Array[Int]) = a.map(_ + i)
}
implicit def array2addable(a: Array[Int]) = new ArraysAdd(a)
implicit def scalar2arrayaddable(i: Int) = new ScalarAddsToArray(i)
and then you can do math on arrays of integers:
scala> Array(1,2,3) + 5
res2: Array[Int] = Array(6, 7, 8)
scala> Array(1,7) + Array(3,2)
res3: Array[Int] = Array(4, 9)
scala> 4 + Array(-2,-3,-1)
res4: Array[Int] = Array(2, 1, 3)
If you want to cover all data types it gets trickier (you need to use generics and Numeric at least--or write a code generator to cover the cases you need), and for efficiency you might want to create a custom "matrix" class instead of sticking with raw arrays that you keep wrapping with extra functionality.
This covers basic operations. For mathematical functions like math.sqrt
, if you don't mind typing a map sqrt
instead of sqrt(a)
then you don't need to do anything. Otherwise, you can overload them all yourself; tedious, but it then lets you use them transparently.
Or you could use a library that has already done much of this for you. (Scalala is the best candidate I know of for matrices.)