What I Wish I Knew When Learning Haskell

89 views
Skip to first unread message

Logan Campbell

unread,
Dec 7, 2013, 9:09:01 PM12/7/13
to fp...@googlegroups.com
Today I stumbled across What I Wish I Knew When Learning Haskell. I found it quite interesting, and I believe it would've been helpful to me had I seen it when I was first trying to learn Haskell.

But under the Applicative Functors heading I found this code:

combined ::
IO String combined = (++) <$> fetch1 <*> fetch2

Which rubbed me the wrong way. This code feels completely opaque to me. I opened up hoogle and found:

<*>: Sequential application.
<$>: An infix synonym for fmap.
(++): I couldn't find. But I assume to be some kind of concatenation using parens to switch from infix to prefix.

I feel that if english versions of these operators had been used I'd have had no trouble understanding the code. The fixity rules also served to confuse me.

So I wonder is there a package somewhere that provides english version of these operators? I suppose fmap exists for <$> but I'm interested in all Haskell operators.

- Logan

Tony Morris

unread,
Dec 7, 2013, 9:28:35 PM12/7/13
to fp...@googlegroups.com
I think English names would detract from obtaining an understanding of what is occurring here.

This code is in a particular pattern. The pattern is a single application of (<$>) with 0 or more subsequent applications of (<*>). It is essentially "lifting to some arity." We can determine the arity by counting the number of (<*>) and adding 1. This is lifting to arity 2. What is it lifting? It is lifting (++).

(++) :: [a] -> [a] -> [a]

So would it mean to lift (++) to arity 2? Since (++) is already arity 2[*]. We would simply apply the Applicative environment to (++).

There to lift (++) we would simply have :: f [a] -> f [a] -> f [a]

So what now? Well, fetch1 and fetch2 are then applied. I am doing this without looking at the reference, but I can promise you that fetch1 and fetch2 satisfy the type f [a].

The return result is the same, f [a] and by virtue of the type annotation on combined, I can see it is the IO applicative, which simply sequences IO effects.

I took 2 minutes to write this email, but about 0.2 seconds to read the code. English would have taken a lot longer because meaning will have been lost. I would encourage developing new tools to derive meaning, rather than fall back to clumsy and unreliable methods such as English.

I am giving a related talk on this in Sydney next week.

[*] Actually all functions in Haskell take one argument (always, there is no exception to this rule), but this is a higher level application of terminology.
--
You received this message because you are subscribed to the Google Groups "fpmel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fpmel+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


-- 
Tony Morris
http://tmorris.net/

Korny Sietsma

unread,
Dec 8, 2013, 12:54:39 AM12/8/13
to fp...@googlegroups.com

> English would have taken a lot longer because meaning will have been lost.

For you, as someone who knows those symbols, sure. However the question was for someone learning Haskell, and for them this is not the most useful response.

- Korny

Tony Morris

unread,
Dec 8, 2013, 1:39:43 AM12/8/13
to fp...@googlegroups.com
On 08/12/13 15:54, Korny Sietsma wrote:

> English would have taken a lot longer because meaning will have been lost.

For you, as someone who knows those symbols, sure. However the question was for someone learning Haskell, and for them this is not the most useful response.


I think I will never appreciate this misleading, dead-end pedagogy.

No, this is not about "me" because "I have learned Haskell." In fact, this context has almost nothing at all to do with Haskell, except that Haskell syntax just so happened to be used for the example. This has to do with concept identification. Specifically, the concept denoted by the symbols used, whether they are English names or not.

I will make the strongest of assertions; not a single person has ever come to understand the concept denoted herein and subsequently said, "well thank you for using [or not using] English names here -- it truly helped me in coming to understand." Certainly, nobody has ever said or indicated it to me or to any of the people in this world who are in the business of helping others come to terms with these concepts. I strongly suspect that nobody ever has. If you'd like to disagree, then please go ahead and help us who currently all agree on this point -- we would love to know why it is wrong. Honestly, we are constantly refining techniques for helping others in this context, and on this point, we have agreed and moved on. I am open to suggestions why, but I do ask that this suggestion is well thought out.

The symbols used here are merely Haskell-specific notations from the original paper[1], which uses its own denotational semantics for the concept. It has nothing at all to do with Haskell. Coming to terms with understanding what it means to lift an arity-2 function into an Applicative environment is completely unrelated to any programming language, and therefore, whether I am or am not intimate with Haskell syntax.

In my eyes, by insisting on this broken effort toward learning concepts you do the following:
* indicate a lack of understanding of the concept involved
* indicate a misidentification of the necessary process to come to understand
* insist that others also fall victim to this misidentification

Therefore, I am compelled to defend curious people against this bad advice. And it is very bad advice indeed, because to a naive observer, it appears to be both helpful and convenient. It is only convenient to the extent that an illusory understanding is convenient and I am not in the business of supporting this.

Now, if anyone is interested in coming to properly understand the concept involved, then I am all for it. I am even employed to help *you* come to understand. It is a significant part of my full-time role. But make no mistake, I won't be riding any on any of this pretend convenience. It will be hard, rewarding work for both of us and I will not be entertaining the dangerous ideas espoused here. No distractions. No nonsense. Let me know.

Ken Scambler

unread,
Dec 8, 2013, 5:13:55 AM12/8/13
to fp...@googlegroups.com

http://www.soi.city.ac.uk/~ross/papers/Applicative.html

The "Applicative Programming With Effects" paper by McBride & Paterson, where applicative functors ("idioms") were originally proposed, is actually a really good read to get a deeper understanding.  Certainly tough at first, but well worth however far you get through.

They introduce "idiom bracket" notation to give the intuition of a regular function call lifted into a certain context - where the arguments are already in that context.

Regular function call with 3 args:
f a b c

Same but lifted:
[| f a b c |]

Haskell doesn't support the "banana brackets" though, although the idea gets kicked around every now and then.

The other intuition is that they are a bit like monads, except more general and less powerful. For instance, they compose, unlike monads. So if both a monad bind and an applicative lift will do the job, you should prefer applicatives as the less powerful construct.

So rather than >>= and unit, Applicatives get <*> and pure -- as Tony mentioned, it looks like wagon wheel symbol used in the paper.

So it turns out the "intuitive" banana bracket version actually is shorthand for:
pure f <*> a <*> b <*> c

f gets lifted, and curried against each (already lifted) value in turn until the whole function is spent and an applicative value is returned.

The paper also proposes <$> as an infix alias for fmap, to make this less verbose:
f <$> a <*> b <*> c

And so that's what you see in Haskell: it's still easy, if you know to look, to see the "lifted function call" intuition in the symbolic notation. It would be very difficult, I think, to recognize this intent with verbose English labels defaulting to prefix form.

Maybe it would be temporarily useful for learning, but then again, probably not.  I'm with Tony on this one.

--

Logan Campbell

unread,
Dec 9, 2013, 6:46:43 PM12/9/13
to fp...@googlegroups.com
I think I've got to a point where I understand what's going on in the code now. Could you confirm that the following pseudo-code is on the right track?

(apply (lift concatenate) fetch1 fetch2)

Fetch 1 and 2 are IO lists. Concatenate (++) doesn't know how to work on IO lists, only normal lists. So lift (fmap, <$>) creates a new version of concatenate that can work on IO lists. This new version of concatenate is then applied to fetch1 and fetch2 by apply (<*>). Or have I misunderstood?

Tony Morris

unread,
Dec 9, 2013, 6:50:36 PM12/9/13
to fp...@googlegroups.com

Yep exactly.

A deeper analysis reveals that lift2 (++) is itself just lift2 with some superficial twiddling about. Note the similarity in the types of (lift2 (++)) and raw (++).

Keep practising, ignore identifier names, and comprehension will become brief and efficient with minimal error.

Logan Campbell

unread,
Dec 9, 2013, 6:59:17 PM12/9/13
to fp...@googlegroups.com
By lift2 do you mean liftA2?

Tony Morris

unread,
Dec 9, 2013, 7:00:35 PM12/9/13
to fp...@googlegroups.com

Yeah liftA2 liftM2 same same, historical accidents aside.

Lyndon Maydwell

unread,
Dec 9, 2013, 7:22:16 PM12/9/13
to fp...@googlegroups.com
There is one advantage I encounter fairly regularly that English names would have. That is, an intuitive pronounceability in spoken conversation. There are certain conventions that one becomes accustomed to after a while, such as ">>=" ~> "bind", but this isn't immediately discoverable. Although, this doesn't really have anything to do with English, per-se.

Andy Kitchen

unread,
Dec 9, 2013, 7:46:53 PM12/9/13
to fp...@googlegroups.com
There has generally been a conflation of syntax and semantics ITT.

It strikes me that Haskell syntax does not have the same austere
uniformity that the semantics have.

Too often Haskell (and Scala) try to have ascii-art pseudo-maths
everywhere. When it works, it's no better than S-Expressions.
When it doesn't work, it's obtuse and vestigial.

Syntax should be a window into pure semantics, not something
that one should have to bend into shape.

The lambda calculus has around 3 reduction rules.
Why not prefer grammars with the same elegant simplicity?


Hope that's some food for thought.

AK

Andy Kitchen

unread,
Dec 9, 2013, 8:13:07 PM12/9/13
to fp...@googlegroups.com
Also, the S-Expression version of the program under discussion actually comes out quite nice.

(<$> ++ fetch1 fetch2)

That just popped into my head.

AK

David Overton

unread,
Dec 9, 2013, 8:17:09 PM12/9/13
to fp...@googlegroups.com
There is also the function "ap", which is a synonym for <*>.  Well, it would be except that for historical reasons it only works on Monads, not Applicative functors.  However, since IO is a monad your original
example could be written as

(++) `fmap` fetch1 `ap` fetch2

And if you don't like ++ you could use the (more general) Monoid function mappend:

mappend `fmap` fetch1 `ap` fetch2

Then maybe ditch the backtick operator syntax:

ap (fmap mappend fetch1) fetch2

All your operators are now functions with names derived from English words.  However, I'm not convinced that this improves the readability or understandability. And Hoogle allows you to look up operators just as easily as other functions.

f <$> g <*> h ... is an idiom in Haskell (pun intended :)) and once you get used to it the syntax is quite  readable and easy to recognise and understand (IMHO).

Haskell has a set of core operators, including ++, <$> and <*>, which you really need to know to use the language.  Most of them are very general and useful.  It is unfortunate though that some libraries go overboard with defining new operators.

David

--

Tony Morris

unread,
Dec 9, 2013, 8:30:38 PM12/9/13
to fp...@googlegroups.com

I completely agree that pronunciation is important. I have regularly discussed integrating this into a language to exploit further benefits.

This is a bit different to using English names.

Two plus two equals four. Nope not even children fall for that one.

Tony Morris

unread,
Dec 9, 2013, 8:33:09 PM12/9/13
to fp...@googlegroups.com

As an interesting point of note Haskell is about to repeat this exact same historical mistake. (<*>) does not belong on Applicative for the same reason it doesn't on Monad (name aside).

David Overton

unread,
Dec 9, 2013, 8:56:59 PM12/9/13
to fp...@googlegroups.com
On 10 December 2013 12:33, Tony Morris <tmo...@tmorris.net> wrote:

As an interesting point of note Haskell is about to repeat this exact same historical mistake. (<*>) does not belong on Applicative for the same reason it doesn't on Monad (name aside).


I'm curious.  Why doesn't <*> belong on Applicative, and where does it belong?  I thought <*> was the whole point of Applicative. 

Ken Scambler

unread,
Dec 9, 2013, 8:56:48 PM12/9/13
to fp...@googlegroups.com
I note Scalaz splits off Apply -- what do you get from Apply instances that don't have a pure operation?  What's a good example? 

(cf. NonEmptyLists as a example of a Semigroup where Monoid cannot apply)

Cheers,
Ken


On 10 December 2013 12:33, Tony Morris <tmo...@tmorris.net> wrote:

Tony Morris

unread,
Dec 9, 2013, 8:58:52 PM12/9/13
to fp...@googlegroups.com
You can get liftA2 from Apply. The pure operation is not needed.

http://blog.tmorris.net/posts/2013-10-18-applicative-do.html

Ken Scambler

unread,
Dec 9, 2013, 9:06:04 PM12/9/13
to fp...@googlegroups.com
Good point Andy.  This is a slam-dunk great feature of Lisp that too often gets paraded as a disadvantage.   What does Haskell get from optional infix syntax?  Is it really more readable in some cases, or is it just a matter of familiarity and convention?

I do kinda like having infix syntax, but I'm not at all sure that I like it for good reasons.  Probably just familiarity and inertia, at the cost of a more complicated parsing structure, and less generality.

Tony Morris

unread,
Dec 9, 2013, 9:08:27 PM12/9/13
to fp...@googlegroups.com
(f . g) has been around a while.
So has (2 + 2).

So yeah, it's just familiarity..
-- 
Tony Morris
http://tmorris.net/

Reply all
Reply to author
Forward
0 new messages