[clean-list] ADTs for fields in record types, please!

Marco Kesseler m.wittebrood@mailbox.kun.nl
Wed, 15 Oct 2003 00:32:37 +0200


On Tue, 2003-10-14 at 04:56, Fergus Henderson wrote:
> On 14-Oct-2003, Marco Kesseler <m.wittebrood@mailbox.kun.nl> wrote:
> > 
> > The main problem is that the "Integer" object below exposes too much 
> > of its internal types. There should be no reason to tell the outside 
> > world that inc works on the "i" field like this.
> > 
> > :: Integer = E.i: {
> >    value	:: i,
> >  		inc		:: i -> i,
> >  		print	:: i -> String
> >  	}
> 
> But "i" here is completely abstract.  There's really no exposing of
> internal implementation details of `i' going on here.

Yes, but I was not talking about the internals of 'i'. I was talking
about the internals of 'Integer'. If you add some additional field to
the Integer object, one would have to "expose" the (possibly concrete)
type of this field as well in the methods that use it. And one would
have to adapt the wrapper functions.

:: Integer = E.i {
  value            :: i,
  numberOfIncCalls :: Int,
  inc              :: Int i -> (Int, i)
  print            :: i -> String
}

Now you could argue that the complete state of each object should be
existential, and that any new fields should be added IN this type (i.e.
within the definition of i). If that is what you are proposing, you may
be right: little seems lost if people know that the methods act on this
type. And indeed: this would really hide ALL fields. 

But then suppose that the "print" function suddenly needs to call "inc"
internally. Must we then change the type of the print function into:
print :: (i -> i) i -> string, and adapt the wrapper functions as well?
Note that we cannot move the inc function into the existential "i", as
then it becomes inaccessible for everybody except "print".

> > So we end up having to define "wrapper" functions to hide this flaw 
> > (like the ones you suggested), and this introduces extra work.
> 
> The wrappers are indeed a little bit of extra work.  So it would
> be nice to be able to use a type-class constrained existential type,
> like you can do in Glasgow Haskell and Mercury, e.g. something like
> (inventing new Clean syntax, and overloading the name "Integer" as
> both a type class and a type):
> 
> 	:: Integer = E.i | Integer i: { value :: i }

I agree that this kind of functionality is needed. On the other hand,
for "object"-related stuff I would very much like a notation that more
closely matches object oriented languages (i.e. define the data and its
methods in one construct).

> It would even be nice to have such a type generated automatically from
> the class declaration, and to have the instance generation that makes
> the type be an instance of the class be similarly generated automatically.
> (Of course this only works in a restricted set of cases, not for all
> type classes, e.g. it only makes sense for single-parameter type
> classes, not multi-parameter type classes.)

I don't quite follow this. If an object has more than one existential
type, how do you then define an interface for it using a type-class
constraint?

> > Things 
> > would be simpler with objects like the following, as they would allow 
> > one to specify that they operate on the object they are contained in, 
> > without reveiling on what fields exactly. Dealing with these fields 
> > would then become purely a matter of the method itself, not of some 
> > external wrapper function.
> > 
> > :: Integer = E.i {
> > 		value	:: i,
> > 		inc		:: Integer -> Integer,
> > 		print	:: Integer -> String
> > 	}
> 
> Now here I disagree.  Using an approach like that isn't simpler at all, IMHO.
> Firstly, like the original work-around, but unlike the solution using
> typeclass-constrained existential types, you've duplicated the "inc"
> and "print" members from the class in the data type.

Ehm, I suspect that I didn't express myself clearly enough. The
(invented!) notation above would actually BE the class definition. I
should have made clear that I was not talking about any real Clean
syntax here (the compiler won't accept the construct above).

> Secondly, it is fundamentally not type safe, because it allows you to
> apply methods from one type of Integer to a different type of Integer.

Only IF the compiler would allow you to apply one of the methods to
something else than their own object. I would propose that it would not.
More precisely, I would propose that the compiler always apply these
methods to their own object implicitly, just like many OO
implementations do.

regards,
Marco