StanzaOrg / lbstanza-old

L.B. Stanza Programming Language
Other
216 stars 23 forks source link

Extensible statical typed Libs #32

Closed porky11 closed 8 years ago

porky11 commented 8 years ago

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.

CuppoJava commented 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.

CuppoJava commented 8 years ago

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

porky11 commented 8 years ago

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

CuppoJava commented 8 years ago

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.

porky11 commented 8 years ago

and why are there as well multimethods as function overloading then? Wouldn't it be better to use multimethods everytime?

CuppoJava commented 8 years ago

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

porky11 commented 8 years ago

ah ok, so it would even make sence to define plus and similar as multimethod by default

porky11 commented 8 years ago

I hope the macrosystem can write all methods better, it's much unneccessary effort writing 100 methods just for having usual math functionality