
On Wed, Jan 18, 2012 at 1:09 PM, Matthew Farkas-Dyck
On 18/01/2012, Greg Weber
wrote: On Fri, Jan 13, 2012 at 8:52 PM, Simon Peyton-Jones
wrote: But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
So I understand that internally a Has constraint is great for resolving the type. What is the use case for having the Has abstraction externally exposed?
I think there is a great temptation for this because we would have a functionality we can point to that has some kind of extensible record capability.
But I believe the Has abstraction to be a form of weak-typing more so than a form of extensibility. Just because 2 records have a field with the same name, even with the same type in no way signifies they are related. Instead we need a formal contract for such a relation. We already have that in type classes, and they can already be used for this very capability in a more type-safe way.
My point is that Has abstractions are weak types and that likely we should be searching for something stronger or using type classes. If I am wrong then we should have convincing use cases outlined before we make this a goal of a records implementation, and still I don't see why it needs to be a blocking requirement if we are just trying to solve the basic records issue.
Greg Weber
Has *is* a type class. It can be used and abused like any other. Record members with the same key ought to have the same semantics; the programmer must ensure this, not just call them all "x" or the like.
Weak types these are not. The selector type is well-defined. The value type is well-defined. The record type is well-defined, but of course we define a type-class to let it be polymorphic.
I think the concern is -- similarly to "duck typing" -- that unrelated modules or libraries might unintentionally choose the same name for their record fields. This doesn't seem so theoretical. That they would also choose the same -type- is less likely, but the possibility is there. (Especially for things like, say, size :: Int, or name :: String, or that sort of thing.) The way type classes solve this for functions and methods, as compared to duck typing, is that you have to explicitly declare an interface (type class) - it's not merely implied by the name and type - and for types you have to explicitly declare that they support that interface (with an instance). Classes and their methods are distinguished not just by their name, but also by the module they were defined in. So the analogous thing for records, I think, if you want to be super-safe about it, would probably be to explicitly declare the existence of a record field/selector/projector (do we have an agreed upon terminology?) -- I'm not sure whether the type would be declared for the selector or in the record... module ModuleA where selector fieldA :: Int selector fieldB :: a ...and in a potentially different module... module ModuleB where import ModuleA data SomeRecord = SomeRecord { fieldA, fieldB :: String } -- again, I have no idea where or how the types should be handled ...and in a third module... foo :: Has r fieldA (Int?) => r -> Int foo r = r.fieldA If there were more than one 'selector fieldA' declaration in scope, you would have to disambiguate them with the module name in the same way as all other things: foo :: Has r ModuleA.fieldA (Int?) => r -> Int foo r = r.(ModuleA.fieldA) I haven't thought this through and am not in any way recommending it (especially not any of the specific details); I just saw a parallel. (I *am*, however, uncomfortable with using straight-up type level strings, without consideration for any particular alternative. If nothing else they should at least be opaque symbols which can be passed around and used in the supported contexts but not manipulated as strings. String-based hackery should be left to Template Haskell, and out of the type system. I can't really express at the moment why in particular I think it would be bad, but it feels like it would be bad.)