[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