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

Fergus Henderson fjh@cs.mu.OZ.AU
Wed, 15 Oct 2003 12:15:15 +1000


On 15-Oct-2003, Marco Kesseler <m.wittebrood@mailbox.kun.nl> wrote:
> 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.

But, other than adding additional higher-order function terms corresponding
to additional methods, you *won't* change the Integer object.

> :: 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).

Exactly!

> 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. 

Right.

> But then suppose that the "print" function suddenly needs to call "inc"
> internally. Must we then change the type of the print function

No... why would you need to do that?

print just calls inc.

> > 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.

I'm just saying that if you have a type class declaration such as

	-- Haskell syntax
	typeclass Foo a where
		foo a -> Int
		bar a -> a -> a
		...

then it would be nice if the compiler would automatically generate
a corresponding existential type AnyFoo

	-- (Glasgow) Haskell syntax
	data AnyFoo = forall a . Foo a => MkAnyFoo a

and a corresponding instance declaration

	-- Haskell syntax
	instance Foo AnyFoo where
		foo (MkAnyFoo f) = foo f
		bar (MkAnyFoo x) (MkAnyFoo y) = bar x y
		...

Though probably this would work best if it didn't actually add the "Any"
and "MkAny" prefixes that I used above, and instead just overloaded the
name "Foo" as three things: a type class name, a type name, and a data
constructor name.

My parenthetical remark above was just saying that if you have a type
class declaration for a multi-parameter type class, e.g.

	typeclass Quux a b where
		...

then you don't want the compiler to automatically generate a
corresponding existential type and instance declaration, since
they wouldn't make sense.

> If an object has more than one existential
> type, how do you then define an interface for it using a type-class
> constraint?

I'm not sure what you mean by "more than one existential type".

If you are talking about an object that can satisfy more than one interface,
then you can define a type class for each interface.

> > > 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).

The code above looks like valid Clean syntax to me, with an already-defined
meaning.  Oh, apart from the fact that it is missing a ":" after the "E.i" --
is that what you meant?  I thought that was just a typo.

If you are proposing that the presence or absence of a ":" here have some
semantic meaning, then I think that is a rather confusing and error-prone
choice of syntax.

If you are proposing a new meaning for what is already valid Clean syntax,
then that is definitely confusing and IMHO is not a good idea.

-- 
Fergus Henderson <fjh@cs.mu.oz.au>  |  "I have always known that the pursuit
The University of Melbourne         |  of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh>  |     -- the last words of T. S. Garp.