
Hi, these days I'm thinking about name scoping in Haskell and a question built up silently but steadily in my mind. Many times I see code like this: data Foo = { fooBar :: Int, fooSay :: String, fooClose :: String } which reminds me of "Ye Olde Times" of C where you prepend the structure name (or an abbreviation) to the structure's fields so as to avoid clashes with other (possibly included) structures (here Haskell qualified imports don't help as they just let you cut down the size of the prefix which is still present though and is module-scoped anyway). One of the most appreciated (at least by me) features of OOP languages like C++, Java, Python and co. is the possibility to name instance methods the same in different classes and have the compiler resolve to the correct implementation (at least indirectly through a virtual table when inheritance is in place) simply looking at the type of the variable which "->" is applied to. The most fulgid example of this is the "open" method, which is likely to be present in lots of classes. Now, in Haskell we have type inference, which is "The Good Thing" as it allows to "validate" your program at compile time. Hence, the idea coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java. Is this correct? Think about this piece of code: data DB = { open :: DBHandle } data FS = { open :: File } foo x = open x or, worse: foo x = open (open x) if the result of the first "open" application should typecheck with the second. Notice that, in my understanding, adding a signature doesn't help as the type checker must guarantee that "foo" code complies with its signature, which is actually impossible if open is left undetermined in its type. Thank you for any comment. Cristiano

Hello Cristiano, Wednesday, January 13, 2010, 9:43:06 PM, you wrote:
coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java.
you are right. we either have ad-hoc polymorphism like in C++ where type of id selected based on type of arguments, like in int open(char *s) int open(int i) or two-way type inference where each id has just one type. otherwise, having both overloaded ids and two-way inference, we will got polynomial raise of complexity of type inferencing. ie. imagine some f = a . b . c . d where a,b,c,d have multiple possible types there is backdorr to this mechanism - you can define foo inside of class. moreover, you can use this to overload struct fields: class HasFoo a b where foo :: a -> b data X = {fooX :: Int} data Y = {fooY :: Char} instance HasFoo X Int where foo=fooX instance HasFoo Y Char where foo=fooY although error messages may be not ideal ;) -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

The usual suggestion I see for this sort of thing is to create a typeclass for the operations you care about[1][2]. For example: ------------------------------------------------------------------------------------------- class HasOpen a where open :: a -> Handle data DB data FS openDB :: DB -> Handle openFS :: FS -> Handle instance DB HasOpen where open = openDB instance FS HasOpen where open = openFS ------------------------------------------------------------------------------------------- Of course, this doesn't allow you to have functions share the same name if they have different signatures, as in your (open :: FS -> File) example. To be honest, I think the C / Haskell approach of unambiguously-identified functions is clearly superior to the C++ / C# / Java "class as namespace" idiom, which has caused me no end of grief. Dynamic languages such as Python and Ruby, of course, can return anything from anywhere. This is nice in some cases, but having used both extensively I think (static typing+inference) is a better solution than dynamic typing.
Now, in Haskell we have type inference, which is "The Good Thing" as it allows to "validate" your program at compile time. Hence, the idea coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java.
Is this correct?
Type inference and static typing are separate; inference makes static typing usable, but static typing makes compile-type correctness verification easier. And it's not inference making your goal impossible, but the fact that Haskell (like C) does not support arbitrary overloading. [1] http://www.mail-archive.com/haskell-cafe@haskell.org/msg64844.html [2] http://stackoverflow.com/questions/1897306/haskell-record-syntax-and-type-cl...

Hello John, Wednesday, January 13, 2010, 10:08:08 PM, you wrote:
Of course, this doesn't allow you to have functions share the same name if they have different signatures
class Open a where open :: a instance Open (Int -> String) where ... instance Open (String -> Int) where ... -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On Wed, Jan 13, 2010 at 2:14 PM, Bulat Ziganshin < bulat.ziganshin@gmail.com> wrote:
class Open a where open :: a
instance Open (Int -> String) where ... instance Open (String -> Int) where ...
The problem with this approach is that you'll need to supply type annotations with basically every use of open, which is even more verbose than prepending the type name. -Edward Kmett

Well, you can get part of the way there, by using a class associated type
class HasOpen a where
type Open a :: *
open :: a -> Open a
This lets you use type inference in one direction, without requiring that
every result be a member of a data family. On the other hand, type inference
just became directional, like in C#. you can't use the type of Open a to
infer the type a because you've lost injectivity.
You'd need a data type family to guarantee injectivity and determine a from
the context of Open a, and that yields an ugly mess. Since each container
would have to yield a distinct value type and interoperability goes out the
window.
On the other hand, the mixture of fixed type slots and type family slots
gets you a pretty good compromise. You typically know the type of the
structs whose members you are asking for.
-Edward Kmett
On Wed, Jan 13, 2010 at 2:08 PM, John Millikin
The usual suggestion I see for this sort of thing is to create a typeclass for the operations you care about[1][2]. For example:
------------------------------------------------------------------------------------------- class HasOpen a where open :: a -> Handle
data DB data FS
openDB :: DB -> Handle openFS :: FS -> Handle
instance DB HasOpen where open = openDB instance FS HasOpen where open = openFS
-------------------------------------------------------------------------------------------
Of course, this doesn't allow you to have functions share the same name if they have different signatures, as in your (open :: FS -> File) example. To be honest, I think the C / Haskell approach of unambiguously-identified functions is clearly superior to the C++ / C# / Java "class as namespace" idiom, which has caused me no end of grief.
Dynamic languages such as Python and Ruby, of course, can return anything from anywhere. This is nice in some cases, but having used both extensively I think (static typing+inference) is a better solution than dynamic typing.
Now, in Haskell we have type inference, which is "The Good Thing" as it allows to "validate" your program at compile time. Hence, the idea coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java.
Is this correct?
Type inference and static typing are separate; inference makes static typing usable, but static typing makes compile-type correctness verification easier. And it's not inference making your goal impossible, but the fact that Haskell (like C) does not support arbitrary overloading.
[1] http://www.mail-archive.com/haskell-cafe@haskell.org/msg64844.html [2] http://stackoverflow.com/questions/1897306/haskell-record-syntax-and-type-cl... _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Now, in Haskell we have type inference, which is "The Good Thing" as it allows to "validate" your program at compile time. Hence, the idea coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java.
Is this correct?
There is a proposed extension which is not implemented but was discussed on the list a while back, maybe you'd find this interesting: http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio... Search back in the archives for TDNR and you should turn up some threads.

I wish to thank all of you for your comments.
In fact, the solutions you proposed mostly coincided with mine
(including the one using type families) but, in my opinion, they are
more cumbersome than the prefixed names solution.
Going back to my example:
f x = open $ open x
where:
data Foo = { open :: Bar }
data Bar = { open :: String }
there are basically two possibilities to assign types to the "open"
functions, either the first is "open :: Foo -> Bar" and the second is
"open :: Bar -> String" or the converse.
It's clear that the type checker might explore these two possibilities
until one of them typechecks. Hence, the possible outcomes of this
search would be:
1 - No possible assignment works so the typechecking phase fail.
2 - One possibility matches, then it's undertaken.
3 - Two or more possibilities match so an ambiguity is found which can
be solved only by the programmer specifying the type of the open
function explicitly, which is just like using the prefix based
solution.
Would you trust a type checker behaving like this? My answer is no, as
the type checker would be making assumptions about my intentions when
I wrote the expression "open $ open x". I would rather prefer the
compiler to signal this as an error, forcing me to be more explicit
about my real intentions that I could actually find to be wrong (i.e.
a bug would be accepted silently).
Concerning TDNR, I read about it just a while ago but, as far as I can
remember, it's just a syntactic dissertation on the subject while the
type checking matter is not touched.
Thank you again.
C.
On Wed, Jan 13, 2010 at 8:28 PM, Evan Laforge
Now, in Haskell we have type inference, which is "The Good Thing" as it allows to "validate" your program at compile time. Hence, the idea coming up to my mind is that type inference actually forbids a type-directed resolution of names as in C++ or Java.
Is this correct?
There is a proposed extension which is not implemented but was discussed on the list a while back, maybe you'd find this interesting:
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio...
Search back in the archives for TDNR and you should turn up some threads.
Cristiano
participants (5)
-
Bulat Ziganshin
-
Cristiano Paris
-
Edward Kmett
-
Evan Laforge
-
John Millikin