What if I want some hybrid? From OCaml, I want the awesome module system and strictness by default. (I'm not dismissing laziness or any other evaluation strategy - just saying strictness is a better default.) From Haskell, I want effects tracked in types, higher-kinded types and painless parallelism.
There's one language I know that gives nearly all of those things, which is Ur (of the ur/web framework fame). Modules, type classes, eager evaluation, HKTs. It even adds features that aren't in either language such as first-class records and row polymorphism, type-level programming, macros and probably more. It's apparently very high performance, and even has a nice C FFI. The major downside is that it's only meant to be used for writing web servers, and for whatever reason, despite the huge number of features it supports, there seems to be little demand (or effort) to broaden its usage beyond this role. It also suffers from poor documentation, both from a language manual perspective as well as the compiler itself, which quite frankly has some of the sparsest comments I've ever seen in a code base of its size (when I was perusing before, there were practically zero comments across tens of thousands of lines of code). So yeah, probably not headed for the mainstream in its current state. Also I don't think there's much in regards to parallism in the language, although this might be able to be put in a library ¯\_(ツ)_/¯
I've played with Ur/Web before, and I'm very impressed with its type system, but unfortunately I don't have much use for it, because, as you mentioned, it can only be used to make Web applications, which I don't find terribly interesting. I even remember telling Adam Chlipala on Freenode at some point in time, that it would be very nice if Ur could be used for more general-purpose programming.
Yes, I asked him a similar question a while back when I was investigating the language. I recall the response was something along the lines of that he wrote the language primarily for research into type-safe web APIs, and the language's features were a means to that end rather than an end in themselves. This is unfortunate because as a language it's one of the most exciting that I know of, but like you I have little interest in web apps.
In fact with its current compiler (AFAIK) it can only be run as a web server, which means it's not even possible to write a program which just writes Hello World to stdout and exits. This was frustrating enough to me that I soon gave up on the language. :(
Scala seems to do all these. Unfortunately it also has a bit of a messy OO system that can make inference more painful than the aforementioned options, but life's about trade offs I suppose...
Scala offers awkward encodings for some of the things you can do with ML modules, but sometimes it falls short. For instance, you can't emulate datatype declarations inside signatures (as opposed to inside modules themselves). Or SML-style datatype replication. Or OCaml-style applicative functors.
Lack of global type inference is far from the only problem with Scala.
It's easy to hard to force Haskell into "not being lazy". I'd say Haskell in lazy-by-default. You could do lazy eval in non-lazy-by-default langs by using something akin to `Promisses` in JavaScript.
A better module system for Haskell is in the makings (Backpack by EZ Yang), but may or may-not be what you are looking for. To my understanding the module system of Haskell is not very limiting at all.
> You could do lazy eval in non-lazy-by-default langs by using something akin to `Promisses` in JavaScript.
In a strict language, a lazy thunk can be represented as a single mutable cell. In ML, thanks to type abstraction, the mutation is confined to the module that implements laziness, and, in the rest of the program, there's no way to tell that anything impure is happening.
> To my understanding the module system of Haskell is not very limiting at all.
Even Haskell's own designers admit otherwise. Haskell actually originated as an attempt to standardize a lazy, purely functional language for the research community. Modules and records weren't very high in the priority list, and it really shows. But sometimes behind a black cloud there's a silver lining, and the limitations of records inspired Haskell programmers to invent lenses, which are very, very, very awesome.
But, to this day, Haskell still doesn't have a decent alternative to ML-style modules and functors. As far as I can tell, in vanilla Haskell (no extensions), you can only encode functors on modules with a single type component. With either type families or multiparameter type classes, you can encode modules with more than one type component, but the result is very awkward.
> In a strict language, a lazy thunk can be represented as a single mutable cell. In ML, thanks to type abstraction, the mutation is confined to the module that implements laziness, and, in the rest of the program, there's no way to tell that anything impure is happening.
Unfortunately, the general story isn't quite so nice, due to thread-safety.
Immutable data structures are great for thread-safety, as there's no problems with data races or generally invalidating things, because there's no writes... but laziness introduces secret writes, among other problems. This means a type providing laziness either can't be used across multiple threads simultaneously, or is forced to have extra overhead (e.g. atomic operations, blocking & registering for being woken up when trying to force a thunk that another thread is already forcing), and users of a library definitely need to know if a module is using mutation in the former way, and may want to know about the latter.
Of course, if shared-memory concurrency/parallelism is forgone, this isn't a problem, but that omission has its own downsides. And... I would suspect the overhead of just implementing it in a thread-safe way can be considered negligible in many cases, especially if data is guaranteed to be pointer-sized (or less).
(Concurrency/parallelism are sometimes described as "abstraction breaking" for this sort of reason.)
In order of less mainstream to more so
* Idris : modules?, strict by default, effect tracking by default, higher-kinded types, parallelism I think (at least on some platforms)
* Ceylon: modules, strictness, experimental higher-kinded types (possibly only on JS), parallelism (possibly only on Java)
* F#: modules, strict by default, parallelism
* Scala: some modularity, strict by default, higher-kinded types, parallelism
None of Idris, Ceylon or F# doesn't have ML-style modules:
(0) “In principle”, you could encode modules in Idris using dependent sum types, but... Good luck with that! It isn't going to be terribly usable. In general, Haskell and related languages don't consider it worth the effort to give modules proper types. I guess we can be thankful Agda's modules don't suck as badly as Haskell's - at least they can be nested.
(1) Ceylon and Scala's type systems are sophisticated enough to encode some of the use cases for modules with objects, but invariably the result is awkward. And other use cases are just impossible. For example, how should I encode an ML functor that takes as arguments two modules with shared type members? If it's possible at all, I don't even want to imagine how horrifying it will be to manually turn all the sharing by fibration into sharing by parameterization.
(2) F# doesn't allow any encoding of ML-style modules. Nothing. Nichts. Nada. It's the only so-called “ML dialect” where the most important feature from ML is completely missing.
You could encode a module as a record with a stored type and functions for it for Idris. Then you can simply create functors as functions that take one type of record in and return another type. The syntax is ugly, but you can define macros to make it slightly more appealing. It's not an optimal approach because of namespacing of record fields, but I remember hearing that one of the next few Idris releases was going to change how record fields get namespaces.
ML's module language has subtyping. Given any module signature, you can obtain a supersignature by forgetting any of the following:
(0) The presence of a value component.
(1) The presence of a type component.
(2) The concrete definition of a type component, while still remembering its presence.
I am not sure you can do this in Idris, other than manually shuffling data between multiple dependent record types. Which is rather inconvenient: If you have a module with 20 components, the last thing you want to do is manually shift 15 of them to another module.
Also, unlike vanilla Haskell (no extensions) type classes, which can only have a single type parameter, ML modules can have more than one type component.
You can somewhat switch Haskell to being strict-by-default with the new Strict/StrictData pragma[0], which should be in GHC 8.0. There's also a great write-up of it here [1].
As someone else mentioned, Backpack is trying to "fix" the module system in Haskell[2], which should be interesting. That said, I don't have much of a problem with it, but then again I haven't seen the light of the OCaml way yet :)
[0] https://ghc.haskell.org/trac/ghc/wiki/StrictPragma https://ghc.haskell.org/trac/ghc/wiki/StrictPragma
[1] http://blog.johantibell.com/2015/11/the-design-of-strict-haskell-pragma.html http://blog.johantibell.com/2015/11/the-design-of-strict-has...
[2] http://plv.mpi-sws.org/backpack/ http://plv.mpi-sws.org/backpack/
The real benefit of using a strict-by-default language is that laziness shows up in types. That is, I can check the type of a variable, and tell whether it's guaranteed to be a value (if it has type `Foo`), or it's a potentially still unforced thunk (if it has type `Lazy Foo`). For instance, the real type of lazy lists would be:
data Front a = Nil | Cons a (Stream a)
type Stream a = Lazy (Front a)
Haskell doesn't do this, not even with the Strict or StrictData pragmas.