Strong duck typing / structural subtyping / type class aliases / ??? in Haskell

Haskell's records are a bit annoying, and type-classes often group together too many methods, which means you make early decisions about future unknown requirements, and IMO you always get it wrong :-) After having read an email in the cafe about the Noop language & Self language, I realized that what I really would like to have is "strong duck typing" on records (or is it called structural subtyping? or prototype-based-objects? or something like that) For example (silly example full of inaccuracies, but you get the picture): class HasPosition a where position :: a -> Point withPosition :: Point -> a -> a class HasVelocity a where velocity :: a -> Vector withVelocity :: Vector -> a -> a which we really should write as field HasPosition :: Point field HasVelocity :: Vector And then record IsKinetic :: HasPosition HasVelocity suppose we write a function like kineticEulerStep dt k = withPosition (position k .+^ dt *^ velocity k) k kineticEulerStep will work on any type a that HasPosition and HasVelocity, and would get inferred signature kineticEulerStep :: IsKinetic a => Float -> a -> a which is identical to kineticEulerStep :: (HasPosition a, HasVelocity a) => Float -> a -> a So basically kineticEulerStep accepts anything that HasPosition and HasVelocity, whatever it is. So if it walks like a duck and ..., then it is a duck, but statically known... We could also do field HasForce :: Vector field HasMass :: Float record IsDynamic :: IsKinetic HasForce HasMass acceleration d = force d ^/ mass d withAcceleration a d = withForce (a ^* mass d) d dynamicEulerStep dt d = withVelocity (velocity d ^+^ dt *^ acceleration d) Of course you would also need type families to be really correct since Vector, Point, etc should also be parametrized. And really kineticEulerStep might also work on something that HasVelocity and HasAcceleration (since the code in dynamicEulerStep is almost the same as kineticEulerStep), so better abstraction might be needed. I'm not sure what kind of overhead a system like this would have in Haskell, since I suspect the many dictionaries are often not optimized away. I think for Haskell prime, something like this was suggestedhttp://repetae.net/recent/out/classalias.html, but is was rejected? Languages like OCaml and haXe http://haxe.org/manual/2_types also provide a similar feature? I would like to collect ways of doing this in Haskell, without boilerplate, and preferably without runtime overhead. I remember reading OOHaskell a while time ago, and while I didn't understand a lot of it, I recall it also was doing a similar thing, but since the compiler lacks native support, the error messages you get most likely make it impossible to figure out what is going wrong. I think Grapefruit's Records, HList, Data.Accessor, etc.. might also work. Any guidelines and comments regarding "strong duck typing"/"structural subtyping" are very welcome, since the lack of this is the only reason why I would prefer a dynamic language over a static one. Thanks a lot, Peter Verswyvelen

Short answer: There is no good way of doing what you want.
This is actually one of my biggest annoyances with haskell (right up there
with disallowing infinite types). They are many techniques that work better
or worse depending on the application, but non are very satisfactory IMO.
Your typeclass solution(or some variant of) is pretty much your best option.
If you're careful about how you define your datatype and classes you can
avoid the type families and such, but the whole point is to not have to be
careful.
If your types are fixed (which is usually true as long as you're not using
existentials) you might be able to get away with using
-XDisambiguateFieldRecords
If you want anything better you're probably going to have to use some form
of preprocessor (like OHaskell).
Supposedly OCaml has an OO feature that does this but I haven't tried it
out.
I would suspect that the reason why haskell doesn't provide duck typeing on
record fields is that analisys for optimizations is much more complicated
(as it currently stands, records are nothing but sugar on top of algeraic
datatypes).
You can end up with all sorts of weird things with duck typeing on record
fields, like unnamed datatypes. For example:
(using class constraint style to inidicate a record field restriction for
lack of a better syntax)
setPosition :: (position a) =>Vector -> a -> a
setPosition v x = x { position = v }
translate :: (position a) =>Vector -> a -> a
translate v x = x { position = v + (position x) }
getPosition :: (position a) => a -> Vector
getPosition x = position x
result :: Vector
result = getPosition $ translate someVector $ setPosition someOtherVector
The type variable 'a' in these functions is never fixed to a specific type,
and it actually doesn't need to be. The compiler would just have to invent a
suitable one (a type with only the field 'position' of type Vector).
Maybe someday haskell will finially implement good, clean, duck typeable,
record functionality. I will be waiting...
- Job
On Fri, Sep 25, 2009 at 9:54 AM, Peter Verswyvelen
Haskell's records are a bit annoying, and type-classes often group together too many methods, which means you make early decisions about future unknown requirements, and IMO you always get it wrong :-) After having read an email in the cafe about the Noop language & Self language, I realized that what I really would like to have is "strong duck typing" on records (or is it called structural subtyping? or prototype-based-objects? or something like that) For example (silly example full of inaccuracies, but you get the picture):
class HasPosition a where position :: a -> Point withPosition :: Point -> a -> a
class HasVelocity a where velocity :: a -> Vector withVelocity :: Vector -> a -> a
which we really should write as
field HasPosition :: Point field HasVelocity :: Vector
And then
record IsKinetic :: HasPosition HasVelocity
suppose we write a function like
kineticEulerStep dt k = withPosition (position k .+^ dt *^ velocity k) k
kineticEulerStep will work on any type a that HasPosition and HasVelocity, and would get inferred signature
kineticEulerStep :: IsKinetic a => Float -> a -> a
which is identical to
kineticEulerStep :: (HasPosition a, HasVelocity a) => Float -> a -> a
So basically kineticEulerStep accepts anything that HasPosition and HasVelocity, whatever it is.
So if it walks like a duck and ..., then it is a duck, but statically known...
We could also do
field HasForce :: Vector field HasMass :: Float
record IsDynamic :: IsKinetic HasForce HasMass
acceleration d = force d ^/ mass d withAcceleration a d = withForce (a ^* mass d) d
dynamicEulerStep dt d = withVelocity (velocity d ^+^ dt *^ acceleration d)
Of course you would also need type families to be really correct since Vector, Point, etc should also be parametrized.
And really kineticEulerStep might also work on something that HasVelocity and HasAcceleration (since the code in dynamicEulerStep is almost the same as kineticEulerStep), so better abstraction might be needed.
I'm not sure what kind of overhead a system like this would have in Haskell, since I suspect the many dictionaries are often not optimized away.
I think for Haskell prime, something like this was suggestedhttp://repetae.net/recent/out/classalias.html, but is was rejected?
Languages like OCaml and haXe http://haxe.org/manual/2_types also provide a similar feature?
I would like to collect ways of doing this in Haskell, without boilerplate, and preferably without runtime overhead.
I remember reading OOHaskell a while time ago, and while I didn't understand a lot of it, I recall it also was doing a similar thing, but since the compiler lacks native support, the error messages you get most likely make it impossible to figure out what is going wrong. I think Grapefruit's Records, HList, Data.Accessor, etc.. might also work.
Any guidelines and comments regarding "strong duck typing"/"structural subtyping" are very welcome, since the lack of this is the only reason why I would prefer a dynamic language over a static one.
Thanks a lot, Peter Verswyvelen
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Fri, Sep 25, 2009 at 8:14 PM, Job Vranish
Supposedly OCaml has an OO feature that does this but I haven't tried it out.
Indeed, OCaml has stuctural polymorphism, it's a wonderful feature. *# let f myobj = myobj#foo "Hi !";; val f : < foo : string -> 'a; .. > -> 'a = <fun>* IIRC, there has been work on Template Haskell for structural polymorphism. -- Alp Mestan http://blog.mestan.fr/ http://alp.developpez.com/

On Fri, 25 Sep 2009 23:25:21 +0200, you wrote:
On Fri, Sep 25, 2009 at 8:14 PM, Job Vranish
wrote: Supposedly OCaml has an OO feature that does this but I haven't tried it out.
Indeed, OCaml has stuctural polymorphism, it's a wonderful feature.
*# let f myobj = myobj#foo "Hi !";; val f : < foo : string -> 'a; .. > -> 'a = <fun>*
IIRC, there has been work on Template Haskell for structural polymorphism.
Structural subtyping/polymorphism: Pros: - an object can be coerced to any compatible type, the types do not have to be specified ahead of time, that is at compile time. Cons: - may be overly permissive; some coercions might not make sense semantically. I wonder how Haskell will minimize the cons, since it is strongly typed. -- Regards, Casey

On Fri, Sep 25, 2009 at 10:55 PM, Casey Hawthorne
On Fri, 25 Sep 2009 23:25:21 +0200, you wrote:
On Fri, Sep 25, 2009 at 8:14 PM, Job Vranish
wrote: Supposedly OCaml has an OO feature that does this but I haven't tried it out.
Indeed, OCaml has stuctural polymorphism, it's a wonderful feature.
*# let f myobj = myobj#foo "Hi !";; val f : < foo : string -> 'a; .. > -> 'a = <fun>*
IIRC, there has been work on Template Haskell for structural polymorphism.
Structural subtyping/polymorphism:
Pros: - an object can be coerced to any compatible type, the types do not have to be specified ahead of time, that is at compile time.
Cons: - may be overly permissive; some coercions might not make sense semantically.
I wonder how Haskell will minimize the cons, since it is strongly typed.
I kind of think there's no real problem here. If you say that you can accept any record with a given set of fields, then you have to make sure you make no other assumptions. This is, in principle, no different from passing a speed Double to a function that expects a mass Double (e.g. it may produce garbage for negative values). In both cases a function that requires extra invariants can enforce it by using a newtype that's constructed and manipulated in a way which preserves the extra semantic rules. -- Sebastian Sylvan

On Fri, 25 Sep 2009 23:25:21 +0200, you wrote:
On Fri, Sep 25, 2009 at 8:14 PM, Job Vranish
wrote: Supposedly OCaml has an OO feature that does this but I haven't tried it out.
Indeed, OCaml has stuctural polymorphism, it's a wonderful feature.
*# let f myobj = myobj#foo "Hi !";; val f : < foo : string -> 'a; .. > -> 'a = <fun>*
IIRC, there has been work on Template Haskell for structural polymorphism.
Structural subtyping/polymorphism: Pros: - an object can be coerced to any compatible type, the types do not have to be specified ahead of time, that is at compile time. Cons: - may be overly permissive; some coercions might not make sense semantically. I wonder how Haskell will minimize the cons, since it is strongly typed. I forgot to add: that even if strong typing could be measured on "sum" numerical scale, I don't know whether Haskell has a higher "measure/metric" than OCaml, in the strong typing area. -- Regards, Casey

Peter Verswyvelen wrote:
After having read an email in the cafe about the Noop language & Self language, I realized that what I really would like to have is "strong duck typing" on records (or is it called structural subtyping? or prototype-based-objects? or something like that)
The common name for (one form of) what you're seeking is "row polymorphism": http://www.cs.cmu.edu/~neelk/rows.pdf This is implemented in OCaml but, like most OO features in functional languages, it is often ignored or forgotten about. As others've mentioned, there are some cases where row polymorphism is really nice, but it's not always semantically coherent. -- Live well, ~wren
participants (6)
-
Alp Mestan
-
Casey Hawthorne
-
Job Vranish
-
Peter Verswyvelen
-
Sebastian Sylvan
-
wren ng thornton