overloaded functions

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

overloaded functions

Paul Kienzle
I've added dispatch.cc to octave-forge/main/miscellaneous.  
This adds the ability to overload functions in octave.

E.g.,

    mark_as_command dispatch
    dispatch sin string g
    function g,'g',end
    sin('x')
    # ==> g
    sin(3)
    # ==> 0.14112
    dispatch min string g
    min([1 2 3])
    # ==> 1
    min('x')
    # ==> g
    dispatch reshape string g
    reshape(1:6,2,3)
    # ==> [1 3 5; 2 4 6 ]
    reshape('x',2,3)
    # ==> g
    help sin
    # ==>
sin is a builtin function
 - Mapping Function:  sin (X)
     Compute the sin of each element of X.
   Overloaded function string:g

If you are using octave 2.1.40 compile it with mkoctfile -DOCTAVE2140
dispatch.cc, otherwise use octave 2.1.43.

Now we need PKG_ADD to contain the appropriate dispatch functions
for octave forge, such as "dispatch find sparse spfind".

Paul Kienzle
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Andy Adler
That's a really useful function, Paul.

Does it add any significant overhead?

Andy


On Sun, 12 Jan 2003, Paul Kienzle wrote:

> I've added dispatch.cc to octave-forge/main/miscellaneous.
> This adds the ability to overload functions in octave.
>
> E.g.,
>
>     mark_as_command dispatch
>     dispatch sin string g
>     function g,'g',end
>     sin('x')
>     # ==> g
>     sin(3)
>     # ==> 0.14112
>     dispatch min string g
>     min([1 2 3])
>     # ==> 1
>     min('x')
>     # ==> g
>     dispatch reshape string g
>     reshape(1:6,2,3)
>     # ==> [1 3 5; 2 4 6 ]
>     reshape('x',2,3)
>     # ==> g
>     help sin
>     # ==>
> sin is a builtin function
>  - Mapping Function:  sin (X)
>      Compute the sin of each element of X.
>    Overloaded function string:g
>
> If you are using octave 2.1.40 compile it with mkoctfile -DOCTAVE2140
> dispatch.cc, otherwise use octave 2.1.43.
>
> Now we need PKG_ADD to contain the appropriate dispatch functions
> for octave forge, such as "dispatch find sparse spfind".
>
> Paul Kienzle
> [hidden email]
>
>


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Paul Kienzle-2
In reply to this post by Paul Kienzle
On Mon, Jan 13, 2003 at 03:07:19PM -0500, David Bateman wrote:
...

> In any case, can I ask you another question. Consider the lines
>
> octave 1> a = gf(1:7,3);
> octave 2> b = [0 a];
> octave 3> c = [a 0];
>
> where the first line constructs a variable in the Galois field GF(2^3).
> I'd like the next two lines to both return variables in the same Galois
> field as the input, with checking that
>
> 1) Matrix and scalar values are valid in the same Galois field
> 2) All Galois variables are defined in the same field.
>
> However what I get is implicit conversion of "a" back to a Matrix and
> "b" and "c" are then matrix values. I can see some code in pt-mat.cc
> responsible for some of this action, but I'd like to know if you have
> any idea about the easiest way to get the behaviour I want.

Octave doesn't (yet) support concatenation of types.

I think the only sensible way to handle it is with a new concatenation
operator.  That means this will be a pretty big patch.

I hope we can assume that size(cat(i,a,b),i) == size(a,i)+size(b,i),
where cat(i,a,b) is the concatenation of a and b along dimension i,
otherwise we will need a separate operator to return the size of the
result so we don't thrash memory when building up a matrix.

Another option would be a built-in concatenation function with the
parser translating [ ... ] into the appropriate cat functions, then
using dispatch to overload the cat function.  But dispatch will need to
be extended to multiple argument dispatch for this to work, and the
mechanism will have to go through feval which will make it slow.

I don't think we can do it with an octave_value method because the type
resulting from a.cat(i,b) depends on both the type of a and the type of b.

Anyone have another suggestion?

BTW, another thing you are going to encounter is that you cannot
save and load your new galois types.  This will require another
largish patch.  This patch will be more tricky because the format
for save/load will affect how the type is rendered.  Octave ascii/binary
will be simple enough since we just need to write some serializing
functions and attach them to the type.  As a bonus, I can use these
for transferring data in an octave client/server application.  

Foreign file formats are a problem since the way the type is
rendered depends both on the type and the format.  For writing we
can finesse this with a write_<format>_<type> function.  Reading
is trickier because we don't know what type it is until we read
it and we can read it until we know what type it is.  Perhaps each
format will have a readtag_<format> function which can tell us what type
the next item is, then we can call read_<format>_<type>.  This
will only work if the format as type tags for each of its fields
but that must be the case otherwise we wouldn't be using the format
as a generic load/save format.

- Paul


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Paul Kienzle
In reply to this post by Andy Adler
Andy Adler wrote:

>[dispatch is] a really useful function, Paul.
>
>Does it add any significant overhead?
>
A lookup in a very small hash table, an extra feval call, so yes it is
a little bit expensive.

octave:10> tic; for i=1:1000, sin(1); end; toc
ans = 0.047647
octave:11> tic; for i=1:1000, cos(1); end; toc
ans = 0.037207

If you wanted to, you could keep the actual function values in the
structure, and so avoid the extra feval.  Since the number of types
we are likely to dispatch against is small, an array is perhaps a
better representation then a map.  You could be using type-ids rather
than strings to compare types.  This might be enough to make the
presense of dispatch unmeasurable.

Paul Kienzle
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

David Bateman-3
In reply to this post by Paul Kienzle-2
>
> Octave doesn't (yet) support concatenation of types.
>
> I think the only sensible way to handle it is with a new concatenation
> operator.  That means this will be a pretty big patch.
>
> I hope we can assume that size(cat(i,a,b),i) == size(a,i)+size(b,i),
> where cat(i,a,b) is the concatenation of a and b along dimension i,
> otherwise we will need a separate operator to return the size of the
> result so we don't thrash memory when building up a matrix.
>
> Another option would be a built-in concatenation function with the
> parser translating [ ... ] into the appropriate cat functions, then
> using dispatch to overload the cat function.  But dispatch will need to
> be extended to multiple argument dispatch for this to work, and the
> mechanism will have to go through feval which will make it slow.

I thought this was the situation since tree_matrix::rvalue seems to be
hard-coded only for the internal types. However, this is really quite an
important piece of code to have since lots of the existing scripts would
just work for new types.

I kind of like the idea of extending dispatch for multiple arguments.
Although you'd have to include a preference where the overload is
valid for ANY or ALL of the arguments of the new type, and have a
means of arbitrating between multi-arg functions that have arguments
from two different overloaded types. The fact is I also already need
this functionality for dispatch, since I overload the filter(b,a,x,si)
function for my Galois type.

> I don't think we can do it with an octave_value method because the
> type resulting from a.cat(i,b) depends on both the type of a and the
> type of b.

Why not, this way we could implicitly include which type takes priority.
That is

    class octave_foo {
        ...
        octave_matrix cat(int i, const octave_matrix& a);
        octave_foo cat(int i, const octave_bool& a);
        ...
    }

However, what might cause a problem is that the order of the
overloaded type relative to an existing Octave type. I.E. if "a" is of
type matrix and "b" is of type foo then the overloaded type will
supply b.cat(i,a) but the in-built type won't supply a.cat(b,i). This
might cause havoc if the matrix concatenation code is written
hierarchically. Perhaps if there we also had a b.prepend(i,a) function
this problem might be avoided by choosing first looking for the a.cat(i,b)
method and if it doesn't exist looking for b.prepend(i,a).

> BTW, another thing you are going to encounter is that you cannot
> save and load your new galois types.  This will require another
> largish patch.  This patch will be more tricky because the format
> for save/load will affect how the type is rendered.  Octave ascii/binary
> will be simple enough since we just need to write some serializing
> functions and attach them to the type.  As a bonus, I can use these
> for transferring data in an octave client/server application.  

In fact this was going to be one of the next issues I tried to address. If
the new types supply the operators >> and << we should be able to use them
for load/save

> Foreign file formats are a problem since the way the type is
> rendered depends both on the type and the format.  For writing we
> can finesse this with a write_<format>_<type> function.  Reading
> is trickier because we don't know what type it is until we read
> it and we can read it until we know what type it is.  Perhaps each
> format will have a readtag_<format> function which can tell us what type
> the next item is, then we can call read_<format>_<type>.  This
> will only work if the format as type tags for each of its fields
> but that must be the case otherwise we wouldn't be using the format
> as a generic load/save format.

If we can get the octave load/save itself going, the foreign file formats
would be a much lower priority issue for me.

Regards
David


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Paul Kienzle-2
On Tue, Jan 14, 2003 at 11:54:03AM +0100, David Bateman wrote:

> >
> > Octave doesn't (yet) support concatenation of types.
> >
> > I think the only sensible way to handle it is with a new concatenation
> > operator.  That means this will be a pretty big patch.
> >
> > I hope we can assume that size(cat(i,a,b),i) == size(a,i)+size(b,i),
> > where cat(i,a,b) is the concatenation of a and b along dimension i,
> > otherwise we will need a separate operator to return the size of the
> > result so we don't thrash memory when building up a matrix.

I was staring at the code last night.  The problem with a pairwise
cat operation is that there is no way preallocate a large enough array
for the resulting matrix because you don't know what type the resulting
matrix will be.  I suppose the interpreter could do a pass to make sure
that all the components are real/complex/string and do the usual
concatenation, otherwise use the cat operator supplied with the type.  
That will preserve performance in the usual case.  

To provide the same performance as builtin types, I think we need a
cattype operator which given a pair of types returns the resulting type.
Then we can tell the type to preallocate an array and use subasgn to
fill the blocks with octave values.  Or maybe a streamlined subasgn which
does not do size checking.

>
> > I don't think we can do it with an octave_value method because the
> > type resulting from a.cat(i,b) depends on both the type of a and the
> > type of b.
>
> Why not, this way we could implicitly include which type takes priority.
> That is
>
>     class octave_foo {
> ...
> octave_matrix cat(int i, const octave_matrix& a);
> octave_foo cat(int i, const octave_bool& a);
> ...
>     }

I believe cat will only see octave_value inputs and expect octave_value
outputs, but that doesn't matter.

Consider the case of a [ A, B ] where A is an octave matrix and B is
octave_foo.  If you want the result to be octave_foo, then you need to
extend the octave_matrix class, which as an add-on type you are not
privileged to do.  As an operator A cat B, you can look up in a table
the function you need to call which knows about both the type of A and
the type of B.  octave_matrix doesn't need to know about octave_foo
because you can put in the matrix cat foo operator when you install the
octave_foo type.

This even works if both A and B are extension types, assuming one or
the other of types A and B installs the foo cat bar operator.  And you
do not need the prepend suggestion I snipped out below.

<snip>


> > BTW, another thing you are going to encounter is that you cannot
> > save and load your new galois types.  This will require another
> > largish patch.  This patch will be more tricky because the format
> > for save/load will affect how the type is rendered.  Octave ascii/binary
> > will be simple enough since we just need to write some serializing
> > functions and attach them to the type.  As a bonus, I can use these
> > for transferring data in an octave client/server application.  
>
> In fact this was going to be one of the next issues I tried to address. If
> the new types supply the operators >> and << we should be able to use them
> for load/save

It may be nice to have binary serializers as well, but in that case we will
also need a way of detecting and correcting byte order.  My preference is
to make the usual case fast, which means saving the file in native order and
doing the correction on load if for some reason it is being loaded on a
different kind of machine.  

I don't know if we should concern ourselves with non-ieee floating point
representations.  If the binary files had some indicator of the
represenation they were stored in, and if all numeric data is read through
an octave supplied function which can read any format into native order
then it doesn't add any complexity to the type supplied binary serializer,
so I guess non-ieee reps aren't so bad.

Paul Kienzle
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Paul Kienzle-2
In reply to this post by David Bateman-3
On Tue, Jan 14, 2003 at 11:54:03AM +0100, David Bateman wrote:
> I kind of like the idea of extending dispatch for multiple arguments.
> Although you'd have to include a preference where the overload is
> valid for ANY or ALL of the arguments of the new type, and have a
> means of arbitrating between multi-arg functions that have arguments
> from two different overloaded types. The fact is I also already need
> this functionality for dispatch, since I overload the filter(b,a,x,si)
> function for my Galois type.

You won't need this for compatibility since unless they've changed things,
matlab dispatches off the first argument.  From the filter page of the
communications toolbox:

   "The vectors b, a, and x must be Galois vectors in the same field"

Which is not to say that it wouldn't useful to dispatch off the complete
type signature.  In cases of conflict we could take them in the
reverse order that the dispatches were specified.  We may need to change
the PKG_ADD logic so that the load path is traversed in reverse order
when searching for PKG_ADD otherwise the user may get unexpected results.
Alternatively, if we find a conflict we could pick the function which
is first in the load path, perhaps with an ambiguity warning.  This is
more expensive but it will be less surprising if the user fiddles the
LOADPATH variable a lot.

Paul Kienzle
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: overloaded functions

Andy Adler
In reply to this post by Paul Kienzle-2
On Tue, 14 Jan 2003, Paul Kienzle wrote:
> On Tue, Jan 14, 2003 at 11:54:03AM +0100, David Bateman wrote:
> > > I think the only sensible way to handle it is with a new concatenation
> > > operator.  That means this will be a pretty big patch.
> > >
> > > I hope we can assume that size(cat(i,a,b),i) == size(a,i)+size(b,i),
> > > where cat(i,a,b) is the concatenation of a and b along dimension i,
> > > otherwise we will need a separate operator to return the size of the
> > > result so we don't thrash memory when building up a matrix.

I like this idea. I've been wanting to support this for the
sparse matrices.

>From an efficiency point of view, "cat" should take all the arguments
to the function at the same time.

Otherwise
    [a,b,c;d,e,f]
will turn into
     cat(1, cat(2, cat(2,a,b), c) ,  cat(2, cat(2,d,e), f) )
which would potentially be much less efficient.

As a minimum, cat should pack all the elements in each direction
     cat(1, cat(2,a,b,c) , cat(2,d,e,f))

> To provide the same performance as builtin types, I think we need a
> cattype operator which given a pair of types returns the resulting type.
> Then we can tell the type to preallocate an array and use subasgn to
> fill the blocks with octave values.  Or maybe a streamlined subasgn which
> does not do size checking.

This sounds good too. An extension function can register the
"cat"s that it can handle with the cattype operator at load time.
This would perhaps look like:

   INSTALL_BINOP(op_mul, octave_complex_sparse, octave_matrix,         cs_f_mul);
   INSTALL_BINOP(op_mul, octave_complex_sparse, octave_complex_matrix, cs_cf_mul);
   INSTALL_BINOP(op_mul, octave_sparse,         octave_complex_matrix, s_cf_mul);
   ...
   INSTALL_CATOP(op_cat, octave_complex_sparse, octave_matrix, my_cs_f_cat_fcn);

> It may be nice to have binary serializers as well, but in that case we will
> also need a way of detecting and correcting byte order.  My preference is
> to make the usual case fast, which means saving the file in native order and
> doing the correction on load if for some reason it is being loaded on a
> different kind of machine.

Currently I believe we have an octave text format, and the various Matlab
compatible and HDF formats. I think it would be better to enhance this support
for new types rather than add an octave binary format.

andy