[1] This is about correct; Haskell dispatches off the is-a ness of the type. However, code you write will generally ask for the has-a relationship:
{- merge used for mergesort -}
merge :: Ord a => [a] -> [a] -> [a]
{- The "Ord a" above asserts that the type "a"
should be an "Ord"inal, i.e. it should support
< <= > >= == methods
-}
merge [] [] = []
merge a [] = a
merge [] b = b
merge a:as b:bs
| a < b = a:(merge as b:bs)
| Otherwise = b:(merge a:as bs)
{- Of course, I haven't programmed Haskell in a while,
so the above code might be wrong. -}
If we had has-a relationships, we might say something like this in Arc:
(def merge (a b)
(unless (and (has-a (car a) 'Ord)
(has-a (car b) 'Ord)
(is (type a) (type b)) )
(err "Type error, needs Ord"))
(if
(no a)
b
(no b)
a
; now we're assured < works
(< (car a) (car b))
(cons (car a) (merge (cdr a) b))
(cons (car b) (merge a (cdr b))) ))
Hmm. LOL. I can just imagine a macro to do that type checking for you:
(type-check (list a b)
(Ord a) ((a . as) (a . as)))
It's beginning to look lot like Haskell ^^. Heck. arc.arc is congruous to Haskell.Prelude
Yeah, I've seen several ways to express has-a relationships. Statically typed languages like Haskell tend to express them as is-a relationships (e.g. Int is-a Ord), since that meshes well with type safety. Languages that embrace duck typing, like Ruby, tend to make has-a relationships implicit; just use the method (or in Arc's case, function) and assume that the receiver will react properly. This meshes well with the late-binding, screw-static-verification philosophies of these languages, which Arc generally shares, and the message-passing object model, which Arc does not.
This is where I assume Arc will end up building its type system, if it ever does embrace a single type philosophy[1]. Even if it doesn't have a message-passing model, its dynamism makes the implicit has-a model a good fit.
The only language I've seen that embraces explicit checks for has-a relationships, as opposed to expressing them as is-a or making them implicit, is Javascript. This is mostly because you're dealing with objects that have an inherently variable and ill-defined interface that you don't know a lot about at compile-time. I think most JS developers view it as an annoying necessity, though, so I'm inclined to believe it's the least pleasant way of dealing with has-a (despite being the most explicit).
[1] Speaking of which, I really think it should. This is sort of implicit in taking part in all these discussions about typing, I suppose, but I wanted to mention it anyway. Any sufficiently powerful, has-a-based type system won't be a constraint on the language, but will rather allow powerful abstractions like Ruby's #each and almkglor's scanner.