Interestingly, heterogenous lists are the only example I ever hear cited for how ML-family type systems can cramp your style. It leads me to wonder if the situation is not unlike Fibbonacci sequences and naive recursion.
Anyway, I find that usually when I want a heterogenous list in Lisp, all I really need is a tuple. I want an ad-hoc way to group some values together (i.e., I don't want to bother creating a named structure), but I generally know the type I want in each position.
In the rare situations where I really do want a heterogenous list, Haskell does make it possible. The standard library has a Dynamic type that stores an arbitrary object along with a first-class manifest type identifier. These type identifiers have to be generated at compile time, but GHC has built-in syntax for this, and if it didn't it could still be implemented as a Template Haskell macro, or failing even that, just done by hand once for each user-defined type. That's all the support that's necessary from the core language -- the rest of the dynamic typing system is just an ordinary library.
Now, granted, if you wanted to use manifest typing for everything in Haskell, it would be ridiculously cumbersome and you'd be much better off just using a dynamic language[1]. But if you use it only where it's needed, then the dynamic casts will bloat your program by a couple symbols per thousand lines, and in return you get programs that damn near always work the first time they compile, along with a few other nicities like the one I mentioned above with map.
[1] There are plenty of cases where the converse is true. To name an obvious one, you could write a set of Lisp macros to implement lazy evaluation. But if you wanted to use them everywhere, you'd be much better off in Haskell.
So do I -- when I'm working in Lisp. Lisp gives you a Swiss army knife and lets you build specialized tools when you want them. Haskell gives you specialized tools and lets you build a Swiss army knife if you want it.
To be clear, this is what ML's variant types are all about. You can easily create a list that contains e.g. both ints and chars:
let mylist = [`Int 5; `Char '5']
Technically the elements have the same compile-time type, but the question is, what practical difference does that make? In what cases are variants an inadequate solution?
Anyway, I find that usually when I want a heterogenous list in Lisp, all I really need is a tuple. I want an ad-hoc way to group some values together (i.e., I don't want to bother creating a named structure), but I generally know the type I want in each position.
In the rare situations where I really do want a heterogenous list, Haskell does make it possible. The standard library has a Dynamic type that stores an arbitrary object along with a first-class manifest type identifier. These type identifiers have to be generated at compile time, but GHC has built-in syntax for this, and if it didn't it could still be implemented as a Template Haskell macro, or failing even that, just done by hand once for each user-defined type. That's all the support that's necessary from the core language -- the rest of the dynamic typing system is just an ordinary library.
Now, granted, if you wanted to use manifest typing for everything in Haskell, it would be ridiculously cumbersome and you'd be much better off just using a dynamic language[1]. But if you use it only where it's needed, then the dynamic casts will bloat your program by a couple symbols per thousand lines, and in return you get programs that damn near always work the first time they compile, along with a few other nicities like the one I mentioned above with map.
[1] There are plenty of cases where the converse is true. To name an obvious one, you could write a set of Lisp macros to implement lazy evaluation. But if you wanted to use them everywhere, you'd be much better off in Haskell.