
Ben Rudiak-Gould wrote:
Have I succeeded in reconciling our views?
Perhaps! In particular, perhaps it's just a pedagogical issue. Note that instead of: data Shape = Circle Float | Square Float the Haskell designers might have used the following syntax: data Shape where Circle :: Float -> Shape Square :: Float -> Shape which conveys exactly the same information, and makes it quite clear that Circle and Square are functions. I often point this out to my students, because I find it less confusing than Haskell's data type declaration, where type constructors and value constructors are intermixed (i.e. "Circle Float"). Would this have been less confusing for you? As for pattern matching, I think the key point relates to Keith Wansbrough's comment that an algebraic data type denotes an initial algebra. If you want to retain referential transparency, each application of the function being pattern-matchined against must yield a unique value (i.e. "no confusion" as Keith describes it). This is guaranteed with a constructor, but not with arbitrary functions. So, another way to look at it is that constructors simply carve out a portion of the function space where this can be guaranteed. That said, there are lots of interesting directions to pursue where pattern-matching against functions IS allowed (requiring higher-order unification and the like). I suppose that topic is out of the scope of this discussion. -Paul Ben Rudiak-Gould wrote:
Paul Hudak wrote:
Oh, I disagree with this point of view. Circle is certainly a value, i.e. a full-fledged function, as Brian Beckman correctly surmised.
Interesting. I don't claim that my viewpoint is the One True Path, but I don't think it's wrong, either. I know you're interested in the teaching of Haskell, and the fact remains that I *was* confused by data constructors when I learned Haskell, and it *did* help me to stop thinking of them as functions. Different people learn in different ways, and that's how I learned; even now I find this view more natural than the view of constructors as functions. The wording of the OP's article made me think that he might be suffering from the same conceptual problem, so I tried to suggest the approach which worked for me.
The Haskell designers did not decide "for convenience" that Circle is the same as \x -> Circle x. Rather, that's a fundamental law (the eta law, to be exact) of the lambda calculus, on which Haskell is based.
I think you're begging the question here -- the eta law applies to functions -- but maybe you're just elaborating on your view rather than arguing for it, as I was. (I.e. I was elaborating, not arguing, when I said that Circle was a function "for convenience".)
The real reason that the Haskell designers chose to have constructors begin with a capital letter is to make pattern-matching clearer.
Certainly it's odd to be able to match on the result of a function. "case factorial (2*3) of factorial n -> ..." won't work, so it's surprising that "case Circle (2*3) of Circle x -> ..." does, if Circle is a function. On the other hand, if "Circle 6" is just a literal value, it's not at all surprising that "case Circle 6 of Circle x -> ..." does what it does. And, at least for me, that extends to "case Circle (2*3) of Circle x -> ..." as well. (*) is being called in this example, and is returning an entirely new value, 6, but Circle is just getting "added on" to that result, and then stripped off again. There's a clear symmetry between construction and deconstruction which doesn't seem nearly as clear if Circle is seen as a function.
It occurs to me that when I talk about functions here, I am talking about Haskell function values, not about functions as equations "f(x) = ...". In particular, one cannot write an invert :: (a->b) -> Maybe (b->a) which never returns a wrong answer, except for invert = const Nothing -- this is why it makes no sense to me to imagine Circle as being a Haskell *value*. I have no problem imagining it as a function in a more abstract mathematical sense; it's just that Haskell function values don't have that extra structure.
The view of Circle that I was trying to express is closer to Prolog clauses. One can assert circle(1.2), and that assertion will match circle(x), but it doesn't really make sense to assert circle, or to try to match it.
Have I succeeded in reconciling our views?
-- Ben