Closed porky11 closed 8 years ago
Yes you can. I wrote up an example, but it exposed a bug in the ambiguous method detection. I'll post it once that is fixed.
Here's the example that I wrote up. We write our own abstract arithmetic package with an abstractly defined "plus" function. I then introduce a new user-defined type called "ComplexNum" and declare it to be a type of AbstractNum and give it its own "plus" implementation.
Writing this exposed a bug in the ambiguous method detection which I just fixed, so it should work on Stanza version 0.9.6 and above.
;Define our own abstract mathematics package
;Import the core library, but add the prefix "core-" to the "plus" function.
defpackage abstract-math :
import core with :
prefix(plus) => core-
;Define the AbstractNum Type
;Int, Double are defined to be AbstractNums as well
deftype AbstractNum :
Int <: AbstractNum
Double <: AbstractNum
;Implement the AbstractNum operations for Int and Double
defmulti plus (a:AbstractNum, b:AbstractNum) -> AbstractNum
defmethod plus (a:Int, b:Int) : core-plus(a, b)
defmethod plus (a:Int, b:Double) : core-plus(to-double(a), b)
defmethod plus (a:Double, b:Int) : core-plus(a, to-double(b))
defmethod plus (a:Double, b:Double) : core-plus(a, b)
;Define abstract definition of point-wise addition of two collections of AbstractNums
defn plus (a:Collection<AbstractNum>, b:Collection<AbstractNum>) :
to-tuple(seq(plus, a, b))
;Extend our AbstractNum type to support complex numbers
defstruct ComplexNum <: AbstractNum :
real: AbstractNum
imag: AbstractNum
defmethod print (o:OutputStream, n:ComplexNum) :
print(o, "%_ + %_i" % [real(n), imag(n)])
;Convert an abstract num into a complex num
defn to-complex-num (x:AbstractNum) :
match(x) :
(x:ComplexNum) : x
(x) : ComplexNum(x, 0)
;Implement the AbstractNum operations for complex numbers
defmethod plus (a:ComplexNum, b:ComplexNum) : ComplexNum(real(a) + real(b), imag(a) + imag(b))
defmethod plus (a:ComplexNum, b:AbstractNum) : a + to-complex-num(b)
defmethod plus (a:AbstractNum, b:ComplexNum) : to-complex-num(a) + b
;Test code
defn main () :
defn test (a, b) :
println("%_ + %_ = %_" % [a, b, a + b])
test(1, 5)
test(1.0, 5)
test(5, 1.0)
test(5.0, 1.0)
test(5.0, ComplexNum(5.0, 1.0))
test([1 2 3 4], [5 2 3.0 1.0])
test([ComplexNum(0.0, 0.0), ComplexNum(2.0, 3.0) 3 4],
[5 2 3.0 ComplexNum(1.0, 1.0)])
main()
Running the above prints out:
1 + 5 = 6
1.000000000000000 + 5 = 6.000000000000000
5 + 1.000000000000000 = 6.000000000000000
5.000000000000000 + 1.000000000000000 = 6.000000000000000
5.000000000000000 + 5.000000000000000 + 1.000000000000000i = 10.000000000000000 + 1.000000000000000i
[1 2 3 4] + [5 2 3.000000000000000 1.000000000000000] = [6 4 6.000000000000000 5.000000000000000]
[0.000000000000000 + 0.000000000000000i 2.000000000000000 + 3.000000000000000i 3 4] + [5 2 3.000000000000000 1.000000000000000 + 1.000000000000000i] = [5.000000000000000 + 0.000000000000000i 4.000000000000000 + 3.000000000000000i 6.000000000000000 5.000000000000000 + 1.000000000000000i]
Cheers. -Patrick
But using multimethods it still will have to check the types at runtime. It would be better to be able to check them at compiletime
The optimizer is free to check the types at compile-time if there is enough information to do so.
For example, calling
plus(3.0, 4.0)
in the above case results in the standard plus
operation for Double
objects being inlined if you compile it in optimized mode.
and why are there as well multimethods as function overloading then? Wouldn't it be better to use multimethods everytime?
Function overloading dispatch happens at compile-time. Multimethod dispatch happens at runtime.
To use a multimethod, you have to declare the multi using defmulti
and then attach individual methods using defmethod
. This means that all the methods should be semantically related. Often, this is not the case, even though functions may happen to have the same name.
Here is an example of where function overloading is appropriate and multimethods are not.
Suppose John writes a package tree
for handling trees,
defpackage tree
defn bark (t:Tree) -> Bark :
grainy(t)
and Susan writes a package dog
for handling dogs.
defpackage dog
defn bark (d:Dog) -> False :
println("Woof!")
Now you write a program that requires both dogs and trees:
defpackage myprogram :
import tree
import dog
bark(Tree())
bark(Dog())
There are two semantically unrelated bark
functions visible. Function overloading will figure out which one you mean to call. Using a multimethod here is inappropriate because neither John nor Susan is expecting their bark
function to be a method of a multi.
-Patrick
ah ok, so it would even make sence to define plus and similar as multimethod by default
I hope the macrosystem can write all methods better, it's much unneccessary effort writing 100 methods just for having usual math functionality
Is it possible to write statical typed math-libraries (like vector-math or math with seqables), that is extendable for new number-types (not only the 5 default types, maybe also complex-vectors)?
defn plus<?T> (x:Seqable<?T> y:Seqable<T>)
doesn't work for this without using dynamic typing.