Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Well, let me take a deeper look at the material. I've based most of my own language work on multimethod systems like those of Cecil, but there's a few things I immediately see on the page:

* Cecil divides its type system from its object-inheritance-overriding system. Huh?

* Cecil is dynamically typed with static sprinkles on top. So we have dynamic vtables, and also F-bounded polymorphism.

* Methods are referred to as being attached to objects, even though they are multimethods. This appears to imply asymmetric multimethods. Again, huh? What a strange design decision to make!

* Cecil offers nothing for dealing with ad-hoc polymorphism (ie: operator overloading). Admittedly, when Cecil was published, type-classes didn't even exist yet.



Methods are referred to as being attached to objects, even though they are multimethods. This appears to imply asymmetric multimethods. Again, huh? What a strange design decision to make!

Method dispatching is done symmetrically on the arguments in Cecil.


OK, I've read their doc now. And what I can say is, they don't solve the problem I've mentioned at all. Their dot-notation is just syntactic sugar for an "ordinary" multimethod call. It does nothing for module selection.

This means you've got to either use qualified module names, make sure never to import two modules that export an identically-named method, or (if the methods have similar signatures) resort to a type-class.


So??? This is how things work in Clojure. And that's just fine.

I guess I just don't understand where you are coming from. It's been pointed out to you that Clojure hackers don't mind importing the generic functions. You replied that then your worry is that it would be hard to preserve type information in a statically typed language that works like Clojure. I mentioned that Cecil is statically typed and preserves type information, with a multi-method system that is somewhat similar to Clojure's. At least as I understand it.

If your entire worry is that you have to import every generic function you want to use, then I guess you and I just don't worry about the same things at all. To me, importing the functions that you use is a feature, not a bug! In fact, when I program in Python, I generally program a lot more with functions than with classes, and yes, in every module I explicitly import every single externally defined function that that module needs. Again, I consider this a feature, not a bug.

What I would object to is having to explicitly import all the method definitions that implement the generic functions used in a multi-method system. That would be insanity, but that's a moot point, as that is never required, as far as I am aware.

Also, common generic functions, however, such as map(), could clearly be automatically imported by the language. E.g., in Predef in Scala, or in built-ins in Python. In fact, in Python, map() is in built-ins, and you don't have to import it. Though, in Python's case, map() doesn't preserve the original container type


I'm not talking about importing the generic functions themselves at all. I'm talking about importing the modules that export those generic functions, so that I'll have the modules in my namespace to be able to name the generic functions.

Again: dot-notation lets me write:

    import scala.collection.immutable.List

    def addn(n: Int) = (list: List[Int]) => list.map(_ + n)
Instead of:

    import scala.collection.immutable.List

    def addn(n: Int) = (list: List[Int]) => List.map(list,_ + n)
See the difference? And then, remember that map() is not a multimethod. It doesn't actually do dynamic dispatch on its first argument, or on any other argument. It's a type-class method; it does static dispatch on the functor type List[].

You cannot define map() as a generic function, at least not in the sense that Common Lisp and Clojure use the term "generic function".

It can be conveniently expressed as a type-class method of a class 'Functor f :: * -> * ' or a trait method for a Functor[A]. Not a generic function.


Also, the way that you apparently like things, because it is ad hoc, has the "draw problem", where you think that you're telling a shape to draw itself on the screen, but actually you've told your robotic cowboy to shoot your friend in the head.

I know that many people think that this is not a problem worth worrying about, but I think that in a large program it is. Accidentally calling a method that means something completely different from what you thought it meant is much less of a problem if you have to explicitly import the interface that the method you aim to call is supposed to be implementing.


> I'm not talking about importing the generic functions themselves at all.

> I'm talking about importing the modules that export those generic functions,

> so that I'll have the modules in my namespace to be able to name

> the generic functions.

I don't see how this is either here or there. I can import modules and then qualify functions within the modules, or I can import functions directly from the modules, renaming then if I want to. There's no difference, between these options, other than a minor difference in what name I use to refer to the function once its been imported.

> Again: dot-notation lets me write:

> import scala.collection.immutable.List

> def addn(n: Int) = (list: List[Int]) => list.map(_ + n)

> Instead of:

> import scala.collection.immutable.List

> def addn(n: Int) = (list: List[Int]) => List.map(list,_ + n)

It isn't dot notation that lets you do that; it's the semantics of how functions are located that lets you do that. Many languages support statically overloaded functions using functional notation. It's also quite easy to imagine a language which uses the syntax f(a) to mean what a.f() means in Scala.

Also, why is this to be considered so desirable? For the performance gain that comes from eliminating dynamic dispatch? In that case, it is certainly possible for a language that supports multiple dispatch to dispatch statically when it can infer that it is safe to do.

> See the difference?

Yes, I quite understand the concept, but it has nothing intrinsically to do with dot notation.

> And then, remember that map() is not a multimethod.

> It doesn't actually do dynamic dispatch on its first argument,

> or on any other argument. It's a type-class method; it does static dispatch

> on the functor type List[].

In Scala? I don't believe that this is correct. As far as I understand, there's a single implementation of map for the entire collection library in TraversableLike, which uses virtual functions provided by the concrete class (and a builder object which gets passed in implicitly) to construct the new container, loop over the old container, and add elements to the new container.

This is similar to how map() works in Python and Clojure, where there is a single implementation of map() which uses dynamic dispatch to loop over the elements of the container. The downside to the way that Python and Clojure doing things is that you get iterators or streams back, not the original container type. But they could return the original container type if containers in Python and Clojure were to have virtual constructors. Then map() could call these virtual constructors to return a container of the same type.

> You cannot define map() as a generic function, at least not in the sense that

> Common Lisp and Clojure use the term "generic function".

Clearly in a statically typed language, you also need some form of parametric polymorphism in addition to what Common Lisp and Clojure provide in order to preserve types. I see no reason why this should be insurmountable.

I've often done things in C++ that wrapped dynamic lookup in a generic function or class, to achieve the types of things that you say require type classes. Another thing you can easily do in C++ is to write template adapter classes to provide something similar to Scala's structural types.

Also, type classes surely don't require dot notation. Haskell is from where type classes originate, and Haskell doesn't have dot notation.


Also, why is this to be considered so desirable?

To avoid namespace pollution.


I just want to reiterate how completely a non-issue this is. It is so much of a non-issue, that it took me this long to even get the slightest inkling of what your worry is.

The reason that it is a non-issue is how often do you need to import two external functions with the same name that have different meaning into the same file at the same time? It does happen, but not so much that it is an issue.

With Python, for instance, there is sys.path and os.path. So what? I refer to one as sys.path and the other as os.path. BFD. And there's the join method on strings and os.path.join. Since one's a method and the other's a function in Python, you're right that if methods were called like functions in Python, there'd be a potential namespace conflict, but I'd just continue to refer to one as join and the other as path.join. I do that now anyway, so nothing would change!

Or let's take the example of draw. If we're only talking about drawing to the screen, then all of the draw methods can implement a common generic function. There's no problem. On the other hand, if I need to use both the "draw on the screen" draw and the "draw your gun" draw, then I'd import one as drawOnScreen and the other as drawYourGun.

If overloading in supported, then there's no need even to do renaming. You can just use draw all the time and the type system will know which draw is applicable for the type in question.

Once again, no problem.

In summary, your worry is just not a problem!


Namespace pollution is not a problem if you are required to explicitly import all external functions into each file. And it's especially not a problem if overloading is supported.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: