Hacker News new | ask | show | jobs
by eli_gottlieb 5193 days ago
As I said above, think about a method like map.

    map :: [a] -> (a -> b) -> [b]
Well, actually, any functor has a map method, by definition! So we'd have to write a type-class:

    class Functor f where
      fmap :: f a -> (a -> b) -> f b
Now how would we represent this as a generic function?

    method fmap<a,b>(as: Functor<a>,f: a -> b): Functor<b>
And then implement specialized methods for each actual functor?

    override fmap<a,b>(as: List<a>,f: a -> b): List<b>
Now everything involving functors performs a dynamic dispatch and might lose type information at its call-site, and we now have to deal with Functor<a> being some kind of superclass, a superclass without data slots of its own just to accommodate our need for this "interface".

In Scala, rather than doing that, we would just define Functor[A] as a trait:

    trait Functor[A] self => {
      def map[B](f: A => B): self[A]
    }
And now every time I call the map method on a Scala object, it statically dispatches via dot-notation. So my compiler knows that calling map on a List[A] gives me back a List[B], and that calling map on an Option[A] gives me back an Option[B], and I didn't need any implicits to get it done.
1 comments

I do not see why your worries cannot be addressed. In fact, IIRC (it's been a while), the programming language Cecil does just that.

http://www.cs.washington.edu/research/projects/cecil/www/cec...

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.