Defining a new type and changing print command for its printing

How can a new type be defined in Maple? For example a type named fst and given as [list of numbers between 0 and 1 inclusive, list of anything].

Further whenever this fst type object is encountered it is printed useing elements of first sublist as subscripts of elements of second sublist.

Thanking in anticipation.

Comments

roman_pearce's picture

extending type and print

Types in Maple are fully extensible. Make a procedure `type/fst` which checks your type. For example:

`type/fst` := proc(L)
   type(L,'listlist') and nops(L)=2 and 
   andmap(proc(a) evalb(a &lt= 1 and a &gt= 0) end proc, L[1])
end proc;

type( [[0.2, 0.3], [x,y]], fst);
type( [[0.2, 2], [x,y]], fst);

Note that the type listlist checks for a list of lists, where all sublists have the same length.

This won't work for printing however. In Maple you can only define how to print a function. Your solution is to make a new function with two arguments (the two lists), for example FST([0.2, 0.3], [x,y]); The lists will get passed to a printing procedure `print/FST` if it exists.

f := FST([0.2, 0.3], [x,y]);  # example object

`print/FST` := proc(a, b) local i;
  [seq(b[i][a[i]], i=1..nops(a))]
end proc:

f;  # look

`type/FST` := proc(f)
   type(f, 'function') and op(0, f)='FST' and nops(f)=2 and type([op(f)], 'listlist') and 
   andmap(proc(a) evalb(a &gt= 0 and a &lt= 1) end proc, op(1,f))
end proc:

type(f, FST);
g := FST([0,2], [x,y]);  # note: still gets printed
type(g, FST);

I hope this gets you started.

JacquesC's picture

Bad style in type

Your

`type/FST` := proc(f)
   type(f, 'function') and op(0, f)='FST' and nops(f)=2 and type([op(f)], 'listlist') and 
   andmap(proc(a) evalb(a >= 0 and a <= 1) end proc, op(1,f))
end proc:

should take full advantage of structured types and should be

`type/FST` := 
    'FST'([list(And(numeric,
                    satisfies(proc(a) evalb(a >=0 and a  <= 1)),
           list(anything)])

This has several advantages. First, it is significantly faster since the overhead of a function call in Maple is so large. Second, it will give better error messages when something goes wrong. Third, the above makes sure that the first list contains numerics before checking for ≥ 0 which can give you infamous "cannot evaluate boolean" otherwise.

Using

type(f, 'function') and op(0, f)='FST' and nops(f)=2 

is particularly hideous. However showed you this as a good way to do things should read a Maple manual from the early 90s or later... The above is assembler-style Maple, shudder. Structured types are your friends. Get comfortable with them. And then you'll see how powerful indets, evalindets and typematch really are.

not so fast

Interesting comment, however, your suggestion has a few problems. First, it doesn't check that the number of elements in the two lists match. Second, there is a typo (missing terminator for the procedure, no biggie). Third, it is not, in fact, faster than Roman's suggestion, but slower---it's half the speed for lists with 10 elements. I suspect that the cause is the use of 'satisfies'. Using, as you suggest, a structured function to replace the "hideous" line reduces the time by about 20%. A more substantial savings can be made by replacing the checking of each index with calls to min and max. The following is about 3x faster than Roman's original:

 AddType('fst3'
         , proc(f)
              ( type(f, 'FST'(list(numeric),list))
                and nops(op(1,f)) = nops(op(2,f))
                and min(op(op(1,f))) >= 0
                and max(op(op(1,f))) &lt= 1
              )
           end proc);

Not knowing what the orignal poster intends, its hard to say whether speed in type checking this data-structure is an issue. If it were, I'd suggest using something simple and fast, say, specfunc(anything,'FST') and doing the validation as a separate step, only when needed. Using the modular approach I suggested, where a new structure is checked at creation, might eliminate the need for re-validation.

TypeTools and Print considerations

Roman's suggestion should get you started, but here are a few finer points to consider. Rather than using `type/FST` to create the type, you might consider using TypeTools[AddType]. Thus

TypeTools[AddType]('FST'
                   , proc(f)
                      ( type(f, specfunc(list,'FST'))
                        and nops(f)=2
                        and type([op(f)], 'listlist')
                        and andmap(proc(a) 
                                     evalb(a >= 0 and a <= 1) 
                                   end proc, op(1,f)));
                     end proc);

An advantage to using TypeTools is that the global name space isn't polluted. Alas, there is currently no equivalent PrintTools package, so to handle the printing you need to assign the global procedure `print/FST`.

You specified that anything could be allowed in the second list. If that is indeed the case (though I suspect names might be more likely) you may need to modify Roman's suggested `print/FST` procedure. Consider what would happen if an element of the list were actually a list, say [a]. Then the if the corresponding 'index' is 0.5 the expression [a][0.5] is created by the print procedure, which generates an error. Another possibility is that the index is 1, which creates [a][1] and so prints `a', not what you wanted.

If that is a concern, one possiblity is to do something like

`print/FST` := proc(a, b) local i;
     if type('FST'(args),'FST') then
         [seq](convert(b[i],'`local`')[a[i]], i=1..nops(a));
      else
         'FST'(args)
      end if;
end proc:

This converts the elements of the second list to local names and then appends an index to them. The reason for adding the conditional was to handle the case of an improperly formed FST element, say FST(1,2,3).

A modular approach

Another way to handle this is to use a module as the data structure. One advantage to using a module is that a ModulePrint member can be assigned; it is used whenever the module is displayed. Type checking can be simplified because modules are created with a constructor that validates the fields. Here is one approach. I chose the names `index' and `value' for the two lists in the old FST data-structure, the names probably are not appropriate.

fst := module()
export New;
local ModuleLoad, printfst;
option package;

    printfst := proc(indx, vals)
    local i;
        [seq](convert(vals[i],'`local`')[indx[i]], i=1..nops(indx));
    end proc;

    ModuleLoad := proc()
        TypeTools['AddType'](':-FST'
                             , m ->  type(m,'`module`'('index', 'value')));                                      
    end proc;

    New := proc(indx :: list(numeric), vals :: list(name))

        if nops(indx) <> nops(vals) then
            error "number of indices does not match number of values";
        elif not andmap( proc(ix) evalb(0 <= ix and ix <= 1) end proc
                         , indx ) then
            error "indices must be between 0 and 1, inclusive"
        end if;

        module()
        export index, value;
        local ModulePrint;

            ModulePrint := () -> printfst(index,value);
            index := indx;
            value := vals;

        end module;

    end proc;

    ModuleLoad();

end module:

To use this you could do

with(fst):
f := New([0.2, 0.3], [x,y]);
type(f,FST);

The above implementation is simplistic. You might not want to export index and value, that allows a user to directly modify them and hence possibly generate an illegal structure. Safer would be to export methods (procedures) that access (and modify) local fields.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
}