Re: Spad and inductive types

"Bill Page" <[email protected]>
Newsgroups gmane.comp.mathematics.axiom.user
Message-ID <[email protected]>
Quoting Ralf Hemmecke <[email protected]>:

>
>>  All the exports that appear are basically the
>>> exports of the Union (OK, Union still has a few more.)
>
>>> ---BEGIN aaa.as
>>> #include "aldor"
>>> #include "aldorio"
>>>
>>> define ET: Category == with; -- ExpressionType
>>> Expr: ET with {
>>>     MkInt: Integer -> %;
>>
>> It doesn't look right to me that the MkInt constructor
>> takes a specific integer as a parameter while MkAdd and
>> MkMul take a type. 
>
> Huh? The MkAdd also take specific elements of Expr. That
> is not different from an element of Integer. It's at the
> same level. 
>

No, I think there is a serious misunderstanding here. What
Gaby is talking about are "algebraic data types". See for
example:

http://en.wikipedia.org/wiki/Algebraic_data_type

In Gaby's example Haskell code:

   data Expr = MkInt Int
           | MkAdd Expr Expr
           | MkMul Expr Expr

MkInt is *not* a function which when given an Integer
returns something of type Expr. It is type *constructor*,
that is, when given 'Int' MkInt returns a subtype of Expr. 
The difference might seem a little subtle at first but we
can clearly distinguish such things in languages like
Spad and Aldor. 

In Spad and Aldor a type constructor is a function that
returns a Type. For example 'Complex' is such a
constructor, so 'Complex Integer' is the domain of
Gaussian Integers. 

Similarly in Haskel 'MkInt' is a type constructor and
'MkInt Int' is an algebraic type, in exactly the same way
that  'Complex Integer' is a domain in Axiom. One should
not be tempted to write: 'Complex 1'. Similarly 'MkInt 1'
would not make sense. 

>>>     MkAdd: (%, %) -> %;
>>>     MkMul: (%, %) -> %;

Here you have not defined algebraic type constructors. We
need 'MkAdd(MkInt Int,MkInt Int)' to be a *type*, like
'Cross(Integer,Integer)' is a type in Aldor. One would not
write: 'Cross(1,1)'. In fact, 'MkAdd(MkInt Int, MkInt Int)'
is another subtype of Expr. Even more generally
'MkAdd(Expr,Expr)' is a subtype of Expr which requires
of course that Expr be at least countably infinite. 

>>>     apply: (%, 'MkInt') -> Integer;
>>>     apply: (%, 'MkAdd') -> (%, %);
>>>     apply: (%, 'MkMul') -> (%, %);
>>>     case: (%, 'MkInt') -> Boolean;
>>>     case: (%, 'MkAdd') -> Boolean;
>>>     case: (%, 'MkMul') -> Boolean;
>>
>> I think having 'apply' and 'case' appear as exports of
>> Expr is very undesirable. 
>
> Well. What do you do with a data structure that has no
> accessor functions?

One is supposed to define functions over an algebraic data
type by recursion. So what in Gaby wrote:

   eval::Expr -> Int
   eval (MkInt i) = i
   eval (MkAdd x y) = (eval x) + (eval y)
   eval (MkMul x y) = (eval x) * (eval y)


in Haskell, by a kind of convenient abuse of notation
(or polymorphism if you wish) 'MkInt' also denotes a
function

   MkInt: Int -> MkInt Int

that creates an object of type 'MkInt Int' from an object
in 'Int'. I think this is a potential source of confusion. 
In Axiom we would usually call this a coercion. 

Similarly 'MkAdd' denotes both a type constructor in
'MkInt Expr Expr' and a binary function

   MkAdd: (Expr,Expr) -> MkAdd Expr Expr

>
>>> } == add {
>>>     Rep == Union(
>>>         Mkint: Integer,
>>>         Mkadd: Record(left: %, right: %),
>>>         Mkmul: Record(left: %, right: %)
>>>     );
>
>> Why not write a Union of the constructors, instead of their
>> representation? I.e. something like:
>
>>      Rep == Union(
>>          Mkint: MkInt,
>>          Mkadd: MkAdd(%,%),
>>          Mkmul: MkMul(%,%)
>>      );
>
> Could probably be done, but how is MkInt different from
> Integer?

'MkInt Integer' is a type distinct from but isomorphic to
Integer so it is "natural" to take Integer as the
representation of the type 'MkInt Integer' and rep as the
coercion. In your case 'MkInt' is the coercion function. 

> What do you gain by introducing the domains MkAdd and
> MkMul?

Well, it allows me to write

Expr: ExprCat
   == add
     Rep == Union(Mkint:MkInt(Integer),
                  Mkadd:MkAdd(%,%),
                  Mkmul:MkMul(%,%))

where Rep is a union of types in ExprCat. Then

   rep: % -> Rep

automatically classifies members of Expr (implements the
pattern matching) and

   per: Rep -> %

injects these subtypes into Expr. 

> If that is needed, it can be done as you showed at
> http://wiki.axiom-developer.org/SandBoxInductiveType . 
> But I thought that the Haskell
>
> data Expr = MkInt Int
>            | MkAdd Expr Expr
>            | MkMul Expr Expr
>
> is more like
>
> Union(Cross(Tag, Int),
>        Cross(Tag, Expr, Expr),
>        Cross(Tag, Expr, Expr)). 
>
> In Aldor the tags appear in front of : so the Cross
> would have one argument less (and I have replaced Cross
> by Record (which is probably not necessary). 

Well yes as a tagged union we might write:

Expr:Type == Union(MkInt:Integer,
                    MkAdd:Cross(Expr, Expr),
                    MkMul:Cross(Expr, Expr));

Aldor even appears to solve this recursion, but it does
not seem to export anything usable by Axiom. See:

http://wiki.axiom-developer.org/SandBoxAldorInductiveTypes

I do agree that as types 'MkAdd Expr Expr' is isomorphic
to 'Cross(Expr,Expr)'. That is why I say MkAdd is a type
constructor. 

>
>>>     import from Rep;
>>>     MkInt(i: Integer): % == per union i;
>>>     MkAdd(x: %, y: %): % == per [Mkadd == [x, y]];
>>>     MkMul(x: %, y: %): % == per [Mkmul == [x, y]];
>>>
>>>     apply(x: %, t:'MkInt'): Integer == rep(x).Mkint;
>>>     apply(x: %, t:'MkAdd'): (%, %) == explode rep(x).Mkadd;
>>>     apply(x: %, t:'MkMul'): (%, %) == explode rep(x).Mkmul;
>>>
>>>     (x: %) case (t:'MkInt'): Boolean == rep(x) case Mkint;
>>>     (x: %) case (t:'MkAdd'): Boolean == rep(x) case Mkadd;
>>>     (x: %) case (t:'MkMul'): Boolean == rep(x) case Mkmul;
>>
>> Why does this construction look so different from the
>> definition of Expr2 in
>>
>> http://wiki.axiom-developer.org/SandBoxInductiveType ?
>>
>>> }
>
> I don't actually understand whether this is meant in a
> positive or negative sense. I would rather call my code
> a variation of yours. 

As explained above, I see major differences. 

>
> The biggest difference probably is that you cannot produce
> that what was in file bbb.as, because in an "extend Expr"
> you would have no way to access the internal structure of
> Expr. 

In

http://wiki.axiom-developer.org/SandBoxAldorInductiveTypes

I show how to use extend. 

>
> As I understood Gaby, he wanted to define just the data
> structure without any additional features like eval or
> coercion to OutputForm. 

I tried to eliminate eval and coercion to OutputForm from
my example in SandBoxAldorInductiveTypes, but I ran into
problems because of the recursion and the fact that I
would have to extend the category ExprCat. Apparently
Aldor does not allow me to do this (yet, see chapter of
AUG on extend). 

>
> To such a data structure one could add features
> (= functions) later without changing or knowing the
> actual representation. 

In my example I do seem to require some knowledge of the
representation (i.e. define Rep == ...). Maybe this could
be avoided if I invented some other interface. 

> That is a step to avoid a lot of (if not all) mutual
> recursion in the construction of the Axiom library. 
>

I still think that is wishful thinking. :-(

Regards,
Bill Page.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.