There are a few missteps in this article. To begin with, the author claims that a variable of type Double is "a function with no arguments". This is not the case [1]; not everything in Haskell is a function. A Double is just that: an instance of the numeric type Double.
They also say that "[T]he Ord "typeclass" ... implements comparisons." But of course the typeclass itself doesn't implement comparisons. A typeclass defines an interface which its instances implement. Consider the Eq typeclass:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
So to make a type an instances of Eq, we need to implement (==) and (/=) functions with the type signatures provided by the class. It's pretty obvious how this should go for the Bool type, so let's define that and make it an instance of Eq.
data Bool = True | False
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False
a /= b = not (a == b)
This is all covered very well by Learn You a Haskell [2].
The points you describe are valid, but there are a few nits:
Regarding your first point, your referenced article makes a misstep of its own, conflating the implementation with the semantics. From the article: "Do some folks believe we’re still doing what Church did, i.e., to encode all data as functions and build all types out of ->?"
From a semantics perspective: Yes. If it makes it easier to reason about, then treat variables as nullary functions. In the Spineless, Tagless G-Machine paper, both values and thunks are represented as closures, which may or may not be evaluated. For example:
Is this a variable or a function? Having a special case for Double ("a closure containing a value") versus [Integer] ("a closure containing a thunk") is a distinction only important once the implementation becomes an issue (ie, lazy evaluation affecting memory usage).
As for your second point, typeclasses can include partial implementations. For example, the Eq typeclass [2] has a minimal definition required, either (==) or (/=), as one can be defined in terms of the other.
Great points! That we can treat infinite streams as having the same semantics as lists is of course dependent on Haskell's non-strictness—it's not the same in ML, for example.
For me one of the more convincing arguments that article puts forward is that Haskell only has unary functions; the type Int -> Int -> Int is just a shorthand for Int -> (Int -> Int), i.e. functions which appear to have more than one argument are considered for the purposes of the semantics to be of the form (λa.(λb.c)).
The core point that I think Conal Elliot tries to make is that as far as the denotational semantics is concerned, Haskell has values of many types, some of which (the ones of (abstract) type a -> a) are functions.
You are of course correct about Eq and partial implementations. For me the key point is that where one function is defined entirely in terms of other functions and class constraints which ultimately rely on the instance implementing those other functions, they encapsulate the logic of the typeclass—in other words, they define a property of the interface. So equality for values of a given type is ultimately defined by the instance, but the relationship of equality to inequality—a property of the concept of equality in general—is defined by the typeclass.
> For me one of the more convincing arguments that article puts forward is that Haskell only has unary functions
It's not specific to Haskell, you can trivially argue that all languages with functions have only unary functions but most languages use tuples as the function's sole parameter.
Haskell even lets you switch a function between the two systems via `curry` and `uncurry`.
> the author claims that a variable of type Double is "a function with no arguments".
I believe he was trying to make the very important point that in a functional language, there is no conceptual difference between a variable containing X of type T and a function with no arguments that returns X of type T.
Ok, but you didn't, really. You gave an implementation difference between the two, but what is the conceptual difference?
Also, the implementation difference is just, well, an implementation detail. If you need to worry about whether a value requires calculation or not, you are likely about to mess things up anyway. Consider the following:
f = (a long calculation of some sort)
main = do
doStuffWith f
doMoreStuffWith f
The compiler may or may not decide that the result of f should be cached, meaning that after the first evaluation of f, f will from then on be a plain value thunk.
Given that evaluation order in Haskell is complex (and something you should try to avoid thinking about as much as possible), this distinction does not make conceptual sense.
Further down in my program listing, there is:
bob = f
Is bob a value, because it takes no parameter? Is it a function because f happens to be a function? Is it really a function but practically a value since f has already been evaluated? Is f so trivial (5 + 2) that its result has already been evaluated and inlined by the compiler?
These questions can be answered, but they shouldn't. Their answers are not obvious from the code, and also subject to compiler decisions.
Therefore, "f = sum [1,2,3,4,5,6]" and "f = 21" are completely conceptually equal. The distinction is not important when you are programming. It may be important from a performance perspective in some instances, but that is usually a sign that you should defer some code to C.
> Is bob a value, because it takes no parameter? Is it a function because f happens to be a function?
Functions are values. But not all values are functions. Functions are values with abstract type a -> a. That's what a function is in Haskell. (Of course, I'm only talking about functions from values to values here, not about functions from types to types and so on.)
Whether bob is a function or not will of course depend on its type. For instance, consider the following definition for f:
f = (+ 5)
Then yes, bob will be a function, with type Num a => a -> a. On the other hand, if you decide to define f as
f = take (10^10) $ [1..]
then it won't. The distinction here is determined entirely at compile time: it's about what type a particular value has.
> Therefore, "f = sum [1,2,3,4,5,6]" and "f = 21" are completely conceptually equal.
Right. They're both values of some concrete integer type.
If you were to act like doubles are (optimized) thunks, would that ever trip you up? Haskell's strictness means that its abstractions don't leak as much. If I'm able to pretend that everything is a function, and doing so won't lead me astray, I don't see the point of caring that some things are really values (for some definition of real). That seems like the sort of detail that Haskell makes a point of protecting you from.
Don't forget currying. Actually, in Haskell, function have exactly one argument. If you need more, just return a function.
-- appears to have 2 arguments
foo :: Int -> Int -> Int
foo x y = x + (y * y)
-- equivalent definition
foo :: Int -> (Int -> Int)
foo x = \y -> x + (y * y)
-- or even more consistently
foo :: Int -> (Int -> Int)
foo = \x -> (\y -> x + (y * y))
Thinking this way is more useful in the long run: it makes partial application more natural.
bar :: Int -> List Int -> List Int
Bar = map (foo 42)
They also say that "[T]he Ord "typeclass" ... implements comparisons." But of course the typeclass itself doesn't implement comparisons. A typeclass defines an interface which its instances implement. Consider the Eq typeclass:
So to make a type an instances of Eq, we need to implement (==) and (/=) functions with the type signatures provided by the class. It's pretty obvious how this should go for the Bool type, so let's define that and make it an instance of Eq. This is all covered very well by Learn You a Haskell [2].[1]: http://conal.net/blog/posts/everything-is-a-function-in-hask...
[2]: http://learnyouahaskell.com/making-our-own-types-and-typecla...