This isn't a precise scientific definition of a functional language, but one that seems to quite cleanly differentiate languages like Haskell and ML from languages like Javascript and C#, all of which have first-class functions.
It's true that Haskell has many immutable types, but that's true of almost any language. Haskell is no more immutable than Java or C#, and certainly nothing like truly immutable languages such as Coq or Agda.
[citation needed]
I've never noticed any particular dislike for IO in the language or community, excepting cases where it was obviously not appropriate. A general feeling that IO is best minimized is common throughout all languages and experienced communities.
> the whole point of ST is that it's safe for it to be implemented using mutation while still being treated as having immutable semantics, this doesn't mean the compiler or library actually use mutation in ST code.
I disagree; ST would be practically useless if it could not be relied upon to use mutation. Its purpose is to signal "this code uses safe mutation", not "this code is immutable * wink* * wink* "
And that isn't enough? 'Use IO only where you must' is not second-class? In any other context, I would definitely see that as second-classness - 'Oh, I only employ black workers when I simply can't get any white workers and must employ them'. IO is first-class only in the restricted and technical sense that yes, the type system will let you pass around IO values and functions.
> I disagree; ST would be practically useless if it could not be relied upon to use mutation.
But it still acts immutable and for all we know, is immutable. Just because some compilers and libraries use mutation doesn't prove your thesis that Haskell is as mutation happy as something like Java. As Isaac Asimov said, the man who thinks the Earth is a perfect sphere is wrong, like the man who thought it flat, but if you think he was just as wrong as the flat-Earther, you're more wrong than them both.
In that case, IO is second class in most languages, including Python, Ruby, C#, Java, C++, and C. I do not consider any of them to be functional.
> But it still acts immutable and for all we know, is immutable. Just because some compilers and libraries use mutation doesn't prove your thesis that Haskell is as mutation happy as something like Java.
No, it doesn't "act immutable"; an ST that acted immutable would be equivalent to State. The only advantage of ST over State is that compilers implement it as a special case of IO, and thus it can be used in place of IO for known-pure mutations.
I fall in the latter camp where functional programming is an approach, a style if you would. Where there are first order functions and the ability to easily manipulate them (C# fails here via verbosity), partial function application (often mislabelled currying), closures, recursion is forefront and immutability is the default. Terseness, algebraic types, tuples and pattern matching are optional but highly recommended (in my opinion Scala and Erlang are not terse relatively, Haskell/F#/Clojure are). I also find inference with static typing to be useful. [edit: by terse I don't mean by lines of code but vaguely, how busy each line is].
This combination leads to an extremely powerful and different way of thinking about and writing code that gives extensibility, composability and isolation for free. A style built around passing and assigning functions - treating functions the way most people treat variables. It also allows you to easily render abstractions of your problems without fussing with boiler plates. In terms of organizing and encapsulation it falls short but these new hybrids are shoring things up.
A language is a functional programming language if its standard library includes and is written specifically for immutable data structures. Mutable data structures, if they exist, are second class citizens.
Whether there is an object system is completely orthogonal. Whether there are first-class functions is also completely orthogonal.