In my most-recent post about Forth, where I talked about POLs (Problem Oriented Languages) I said that I had been toying with the idea of implementing polymorphism.
So that’s what I did. It’s the simplest thing that could possibly work.
The idea is simple enough, and nothing new. When you create variables, you specify a type, and a data. Here’s how you create a float:
create f1 t:flt , 0 ,
create i1 t:int , 0 ,
and a string:
create s1 t:str , 0 ,
T:FLT, T:INT and T:STR implement an array of “virtual functions”. Let’s suppose we only implement two such functions: print, and multiply. You can implement more as required.
Here’s what T:FLT looks like:
create t:flt ' f. , ' tf* ,
As you can see, we store the execution addresses of the functions we want to virutalise. You can see that the print function is just F. The multiply operation, TF*, is almost like F*, but we have to do some extra dereferencing:
: tf* swap t@ f* ;
T@ stands for “typed @”. It’s basically the @ operator, but we have to skip the cell which stored the virtual function pointer, and access the payload. There is a twin function, T!, which is analogous to !. Their implementation is simple enough:
: cell+ cell + ; : t! cell+ ! ; : t@ cell+ @ ;
You might ask why we store the virtual lookup table first and then the data payload second, rather than the other way around. That’s because I wanted to allow the possibility that data occupied more than one cell.
Here’s the implementation of T:INT:
create t:int ' . , ' t* ,
where T* is analogous to TF*:
: t* swap t@ * ;
Finally, for strings:
create t:str ' type , ' terr ,
There is no sensible operation for multiplying strings, so I have defined it in such a way as to produce an error:
: terr "Operation not supported" type cr ;
Let’s test it out. First, with floats:
create f1 t:flt , 0 , create f2 t:flt , 0 , 12.3 f1 t! 11.0 f2 t! f1 f2 t* f1 t! f1 t. cr
Then with ints:
create i1 t:int , 0 , create i2 t:int , 0 , 12 i1 t! 11 i2 t! i1 i2 t* i1 t! i1 t. cr
Strings work in the same way. Notice how setting, printing and multiplying all use the same functions (T!, T., T*).
You can obtain a complete listing for a test program here. Undoubtedly cleanups and improvements can be made, but the implementation is sufficient to give you the gist.
Was the effort worth it? Well, it depends on the trade-off. You get “neat stuff”, but at a performance and complexity cost. There’s more “stuff” that you have to learn and use in the right way.
As ever, Forth is like running with scissors. You get there faster, but it’s not for the flat-footed.