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

Marco Kesseler m.wittebrood@mailbox.kun.nl
Mon, 13 Oct 2003 15:54:19 +0200


In addition to Fergus' answer:

>In Mercury, it is
>
>	:- type my_type ---> some [A] my_constructor(A) => adt(A).
>
>Clean does supports existential types, e.g.
>
>	:: MyType = E.a: MyConstructor a
>
>and as you noted above, Clean supports type class constraints.
>But I don't know if Clean supports existential types with type class
>constraints, or what the syntax for them would be in Clean.
>The Clean 2.0 Report (draft) on the Clean web page doesn't specify
>any syntax for it, AFAICT, so perhaps it is not supported.
 
I am not sure about support in Clean for existential types with type 
class constraints either. In principle, this would seem the best 
solution, although this depends on the kind of flexibility people 
want with respect to dynamically defining and changing behaviours.

With respect to my earlier example, I wish that I could just have 
changed the "Integer" typedef in the following way in order to avoid 
hard-coding the integer (this was actually what I had in mind when 
coming up with the example).

:: Integer = E.i {
		value	:: i,
		inc		:: Integer -> Integer,
		print	:: Integer -> String
	}

The problem with this is, that the compiler starts to complain about 
the original "constructors" (see my previous message). As often, the 
compiler is right: it cannot conclude that the methods we are 
specifying in the constructor act on the same type as the value. The 
only way to avoid this, is to define functions on the internal 
representation directly, as follows (this works, but it comes at a 
cost, see below).

:: Integer = E.i: {
		value	:: i,
		inc		:: i -> i,
		print	:: i -> String
	}

inc :: Integer -> Integer
inc obj=:{inc, value} = {obj & value = inc value}

// The 1.3 compiler cannot cope with the following. Don't know why, 
and I don't know about 2.0
//inc obj = {obj & value = obj.inc obj.value}

print :: Integer -> String
print {print, value} = print value

myInteger :: Int -> Integer
myInteger i = {Integer |
		value = i,
		inc = (+) 1,
		print = \i -> "myInteger: " +++ (toString i)
	}

// etc....

I do find the "constructors" more clear in this situation, but the 
"problem" I have with this, is that the "methods" have to be very 
specific in their public signature about what parts of the object 
they refer to. In the end, this affects maintainability of the code, 
as you have to change the method signatures as soon as they start 
taking different object fields as arguments, which should be 
irrelevant for the "external" interface.  This problem also exists in 
solutions that employ type class constraints: the contraints provide 
an interface on an _element_ of the object, not on the object 
_itself_ (or is there a way?).

There are two alternatives, but both are not quite "right":
- parametrise the Integer type with its internal representation. This 
works too, but exposes part of the object internals (not a pretty 
sight).
- one might resort to using Dynamics. I am not too fond about that 
either - if only for the runtime testing -, but neither am I 
experienced in using them (I am still on 1.3).

Perhaps it would be nice to have an extension that would allow 
writing the following code (haven't really thought this over though, 
so shoot me if there's some bloody issue, figuratively that is).

class Integer = E.i {
		value	:: i,
		inc		:: -> Integer // All methods implicitly have the object itself 
as first argument
  print :: -> String
	}

Integer :: Int -> Integer // A constructor
Integer i = {
  value = i,
  inc = value -> value + 1 // i.e. implicitly generate method code 
like in the original code
  print = toString value
}

And then the compiler should:
- implicitly generate "method" calls for these objects
- avoid the type-system complaining about not being able to determine 
the type of the existential value, as all methods are known to always 
operate on their own object.

So in the end you can either call:

print (inc (inc (Integer 0))

or:

(Integer 0).inc.inc.print // i.e. selection, not function composition

or (not all that different):

print (with (Integer 0) do {inc; inc;})

Hmmm, is there a relation with Monads, but then in a way that people 
with an OO background can understand? Maybe this is nice for passing 
around unique states as well? (Don't know, I am no longer an active 
researcher).

:-)

regards,
Marco