D. Cyganski - July 11-13, 2007 Several non-intuitive problems with overloading and type conversions while developing the biquaternion support function collection. I have extracted the minimum code set to illustrate each of these herein.
We begin by illustrating function calling with variously typed arguments and conversions which we will break in various ways, some understandable, others not(?), below. The cos function will produce float outcomes for float arguments fricas cos(1.237)
Type: Floatcan handle expressions that mix floats and integers fricas cos(1.237/2)
Type: Floatbut will respect an integer expression, as we would want it too, by not evaluating fricas cos(2/3)
Type: Expression(Integer)We can coerce the evaluation as a float by forcing the floating point evaluation of the division and typing of the outcome in a variety of ways. Each of the following forms is effective in some appropriate and understandable way. Some act explicitly on the "/" operator to force a polymorphic choice, others convert the type of the second constant in each expression with then results in a proper implicit selection of which "/" definition to use: fricas cos(2/3::Float)
Type: Floatfricas cos((2/3)::Float)
Type: Floatfricas cos(2/3$Float)
Type: Floatfricas cos((2/3)$Float)
Type: Floatfricas cos(2/3@Float)
Type: Floatfricas cos((2/3)@Float)
Type: FloatBut, as we would expect, it is too late to attempt coercion after the fact: coercion operates "on the surface and not deeply" as illustrated here fricas cos(2/3)::Float However, there is a real need for a deep coercion operator that operates on the inner most atomic constants! Suppose we define: fricas cosf(x:Expression Integer):Expression Integer == 1+cos(x/2) Type: Voidwhich is an example of a simple function that might be defined in the course of typical work. We wish to declare functions as having Integer based arguments and outcomes because this results in behaviors that preserve our representation of Integer fractions, rather than forming approximate decimal expansions, which is perferred for purposes of analytic examination and simplification for both the human and the axiom system. The axiom book and online resources are full of examples in which this choice has been made by the authors thanks to the power of this form of expression - even though it amounts to lying to axiom in many cases as to the ultimate destiny of the function being defined. But woe to us if we wish later to evaluate it in a more general way because it is a tangled web we weave when we practice to decieve: fricas cosf(2/3) fricas Compiling function cosf with type Expression(Integer) -> Expression( Integer)
Type: Expression(Integer)fricas cosf((2/3)::Float) Thus in effect once we wrap a function around an Integer base definition, we are
stuck and unable to evaluate it as a float later, unlike the core basic functions that
can be used either way. This forces us to choose the Float type throughout
at a loss of comprehensibility and analyzability, unless we seek to more than double
our development type by supplying an overloaded Integer base and Float base
version of Bizarrely, the draw function seems to have the power to override the type problem as shown here! fricas draw(cosf(x), fricas Compiling function %B with type DoubleFloat -> DoubleFloat Graph data being transmitted to the viewport manager... FriCAS2D data being transmitted to the viewport manager...
Type: TwoDimensionalViewport?Why can't we grant this deep coercion power to some new form of floating point conversion operation which can be applied at will??? If draw has this power, why not put it in the hands of the user? Alternatively,
it would be best to have a fricas cos(2/3)+1.2323
Type: Expression(Float)In a way, Axiom already has a quantity treated like this - the constant %pi is treated as a special float which remains unevaluated and does not force combination of itself with an Integer and simply results in a new kind of Integer expression of type Pi. fricas 3/4+%pi
Type: Pi
Now let's examine properties and problems with overloading. Define the type Q of Hamiltonian biquaternions fricas C:=Complex Expression Integer
Type: Typefricas Q:=Quaternion C
Type: TypeWhile developing the support functions, this definition of biquat division was introduced to simplify the format of the formulae fricas ((x:Q)/(y:Q)):Q == x*inv(y) Type: VoidBut is this typed function in any way actually restricted to quaternions? On the face, it would appear all is normal, here's an example of integer division fricas x:=15/6 fricas Compiling function / with type (Quaternion(Complex(Expression( Integer))),
Type: Quaternion(Complex(Expression(Integer)))But though the answer was right, the type is now a biquat. If we don't notice this, and procede, some things seem still to act normally, for example, no complaint from axiom with fricas cos(x)
Type: Expression(Integer)Of course we still get a correct answers with fricas cos(1.237)
Type: FloatBut let's try to apply this is a simple mixed float/integer function fricas cos(15.457/6) Obiously the quaternion version of "/" is being invoked despite mismatches of the arguments and the supposed overloading in effect. Well, what if we built a new cosine function that forced the form of of the arguments into certain types to avoid the mismatch? fricas c(y:Float):Float==cos(y) Type: VoidAt first this seems to work, we can still evaluate a float fricas c(1.237) fricas Compiling function c with type Float -> Float
Type: Floatand we can even get a float answer when we introduce the integer coercable biquat variable value generated above!!! fricas c(x)
Type: FloatBut that was only misdirection, because this breaks down for reasonable expressions because of the "/" operation still not being resolved correctly. fricas c(1.237/2) Rather than complaining about it, what if we tried the various coercions that served to solve the similar type conversion problem we had when just dealing with Integer Fraction versus Floats at the top of this page. Our results are mixed! Recall that each of the following worked in the previous case, producing the correct floating result in each case: fricas cos(2/3::Float)
Type: Expression(Integer)fricas cos((2/3)::Float)
Type: Floatfricas cos(2/3$Float)
Type: Expression(Integer)fricas cos((2/3)$Float)
Type: Floatfricas cos(2/3@Float)
Type: Expression(Integer)fricas cos((2/3)@Float) Try these examples with our type constrained function, which has better luck now fricas c(2/3::Float)
Type: Floatfricas c((2/3)::Float)
Type: Floatfricas c(2/3$Float)
Type: Floatfricas c((2/3)$Float)
Type: Floatfricas c(2/3@Float)
Type: Floatfricas c((2/3)@Float) Could the above problems been avoided by not assigning types to the function we defined? Let's repeat the entire above example with this single change for the function c2 fricas c2(y)==cos(y) Type: Voidfricas c2(1.237) fricas Compiling function c2 with type Float -> Float
Type: Floatfricas c2(x)
Type: Expression(Integer)But that was only misdirection, because this breaks down for reasonable expressions fricas c2(1.237/2) and various attempts at coercion also fail-compare these results to the previous ones fricas c2(2/3::Float)
Type: Expression(Integer)fricas c2((2/3)::Float)
Type: Floatfricas c2(2/3$Float)
Type: Expression(Integer)fricas c2((2/3)$Float)
Type: Floatfricas c2(2/3@Float)
Type: Expression(Integer)fricas c2((2/3)@Float) Lastly, we cannot now use the graph function, draw, on such a function since the wrong / function is used, contrary to the bypassing of internal types we saw take place with draw in the example prior to the introduction of operator overloading fricas draw(c(x),
fricas draw(cos(x), |