4 ms·
Every explanation of every Haskell feature I encounter reads the same way as this. You define "return" as "<code>return :: a -> m a</code>" and say this means i
by tjradcliffe 12y ago
Every explanation of every Haskell feature I encounter reads the same way as this. You define "return" as "<code>return :: a -> m a</code>" and say this means it takes "an a" (by which I assume you mean an object of type a?) and returns a "<code>Foo<a></code>" (an object of type <code>Foo<a></code>?).
The problem I have is: what on earth does this have to do with 'm'?
Is 'm' equivalent to 'Foo'?
If so, why don't you mention that? It isn't obvious to a newb who is lost in the welter of syntax.
Does 'm a' mean the same thing as <code>Foo<a></code>? If so, why not use 'Foo' in your Haskell rather than 'm', which is simply introduced without any description, mention or explanation? Is there some significance to the naming? This is the kind of question that a newb specifically cannot answer, and Haskell tutorials are notoriously lax about. They have a strong tendency to assume you know the syntax of the language, which they use to explain the syntax of the language.
You've given a nice, detailed explanation, but have failed to describe what 'm' is, and that means your average newb (me, or possibly even someone much, much smarter than me, as I'm told such people exist in profusion) is left making guesses. I'm pretty sure I know what the correct guesses are, but I really want to know: why did you use a completely different, unrelated, and opaque name for 'Foo' in your Haskell example, and then talk exclusively about 'Foo' in your explanation?
This isn't really a knock at you personally: as I said, every Haskell tutorial I've encountered has similar issues. I'm just deeply curious as to why this is the case (and painfully aware that my own explanations regarding other languages are likely as opaque to newbs as these Haskell explanations are to me.)
- swift 12y agoThe 'm' is a type variable. This is a consequence of a difference in how Java interfaces and Haskell typeclasses are defined. (I'm continuing wyager's analogy between Java interfaces and Haskell typeclasses here.) Here's a sample Java interface: interface Pushable { void push(bool shouldShove); } Here's a similar Haskell typeclass: class Pushable p where push :: p -> Bool -> () In type signatures, an identifier starting with a lowercase letter is a type variable. So 'p' is a type variable here, like the 'm' type variable used in the Monad typeclass. It refers to the type which, in Java terms, implements Pushable. (In Haskell you'd say the type "is an instance of Pushable".) So why this difference? Java, being a C++-style OO language, privileges the implicit first argument. When class Button implements Pushable, you can think of its push method as having a real signature of "void push(Button this, bool shouldShove)". This particular pattern is special in Java, so you don't have to write the "Button this" parameter explicitly. But it's not a special pattern in Haskell, so you do need to explicitly write the 'p' in "push :: p -> Bool -> ()". To drive the point home, maybe one more comparison is useful. In Java, when an interface method needs to take a parameter that has the type of the implementing class, you need to do something that looks a bit more like Haskell. Here's an example: interface Comparable<T> { Ordering compare(T other); } If you're not familiar with this idiom (which is the same as the CRTP in C++), here's how you'd implement Comparable<T>: class Foo : Comparable<Foo> { Ordering compare(Foo other) { ... } } Now compare the type parameter 'T' in our Java Comparable<T> interface and the type parameter 't' in this Haskell Comparable typeclass: class Comparable t where compare :: t -> t -> Ordering They serve the same role - they refer to any type that may implement that interface (in Java) or be an instance of that typeclass (in Haskell). Hopefully this clarifies things a bit.
- wyager 12y agoYou're absolutely right; that is confusing. Swift's explanation in response to your comment is good, so hopefully that helps. There are a few reasons I wrote "m a". When you use a lowercase letter in a type definition (like "m" or "a"), that's a type variable. The type isn't anything in particular. In a function like id :: a -> a that "a" can become anything when you use the function later. So you can use "id" on integers, or floats, or strings, or whatever. And with functions like add3numbers :: Num a => a -> a -> a -> a that "a" can become anything that's an instance of the "Num" typeclass (in Java: anything that implements the "Num" interface). "Num" is a restriction on the value of "a", but "a" is still variable. If I were to write a function that had an uppercase word in the type, like doFooThing :: Foo -> Foo an uppercase type indicates that this isn't a type variable, but rather a specific type. In this case, the function only takes and returns the type called "Foo". So that's why I used a lowercase word in my definition of ">>=", as it works on any Monad, not just Foo. Here's how you actually write out the definition of the Monad typeclass: class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a In the definition of >>=, we don't define it in terms of Foo or any other specific Monad; we define it in terms of a type variable (which is idiomatically "m", for "Monad"). "m" can then be filled in with any Monad (like Foo, or Maybe, or IO) when the time comes to actually use ">>=". You could also define Monad with class Monad qwerty where (>>=) :: qwerty ping -> (ping -> qwerty pong) -> qwerty pong return :: dazzle -> qwerty dazzle As you can see, this is not as nice as e.g. "a -> m a" So it would have been more correct for me to have said "here's the type signature for >>=, and when you use >>= with something called Foo that implements the Monad interface, it <explain >>='s type in terms of Foo>."
- kazagistar 12y agoThe fundamental problem is higher order types. When you define an interface in Java or C#, it has no way of referring to itself. Something like "compareTo" cannot say "the other type must be exactly equal to this". So lets invent such a syntax (using a keyword "as") and write the Monad interface: interface Monad as m { const m<b> bind<a,b>(m<a>, Function<a,m<b>>); const m<a> return<a>(a); } If you fill in the implementing class for m (like IO or List or Maybe), you get the actual type signatures that would maybe be valid in C# or something.