Hacker News new | ask | show | jobs
by seanparsons 1290 days ago
I think most people would find these clearer if they used the functions view/set/etc, rather than the various operators which are just alternatives to those.
2 comments

From the beginning of the article:

> The basic operators here are ^., .~, and %~. If you’re not a fan of funny-looking operators, Control.Lens provides more helpfully-named functions called view, set, and over (a.k.a. mapping); these three operators are just aliases for the named functions, respectively. For these exercises, we’ll be using the operators.

...yes and it would be clearer if they used the named functions
But the person who "is not a fan of funny-looking operators" still has to read them; and that's the part that makes funny-looking operators undesirable to them in the first place!
To be fair, if after reading that paragraph they find it so unsufferable, they can skip the article. The author makes a choice and acknowledges other opinions but, at the end of the day, has their opinion.
> I think most people would find these clearer if they used the functions view/set/etc, rather than the various operators which are just alternatives to those.

Everyone has their own tastes. Someone unfamiliar with the notation of ordinary algebra might argue for "you will have to find two numbers that the difference between the two is 10 (that is, so much as is our number) & that we make the product of these two quantities, the one multiplied by the other, exactly 1, that is, the cube of the third part of the variable" (https://www.maa.org/press/periodicals/convergence/how-tartag...) as easier to understand than "find u and v such that u - v = 10 and u v = 1", but I think most modern readers would agree that people uncomfortable with the algebra are better served by learning how to read the latter than by sticking with the former. (And, I think, also that it doesn't help either to keep the variables but replace the symbolic operations by words: `(and (eq (subtract u v) 10) (eq (mult u v) 1))`, in pseudo-Lisp.)

The thing is, basic algebra notation has two major advantages over ad-hoc operators in some Haskell library: (1) it is widely taught and understood and extremely widely applicable, and (2) each operator has a standard name that is widely explained.

In contrast, most Haskell notation I've seen is either an ad-hoc invention for some library, or it is an ASCII version of notation in a niche domain like category theory. Even Haskell's >>= operator for flatMap/bind seems to be an invention, as far as I can tell the equivalent concept in CT is Kleisli composition, denoted by a sharp sign and the regular composition operator (as far as Wikipedia shows - I'm not formally trained in CT).

Additionally, people rarely if ever give a proper name to this notation in Haskell, making it completely impenetrable to even represent the formulas in your mind. How am I supposed to read `user1 ^. name` ? When I see `∇⨯f` I know how to read it (del cross f, or nabla cross f, or curl f) because that was an explicit part of how I was taught the operation (and note that it is not an arbitrary digraph, it can really be computed as the cross product of the pseudo-vector nabla and f), but Haskell tutorials and documentation completely skip this step, in my experience.

> The thing is, basic algebra notation has two major advantages over ad-hoc operators in some Haskell library: (1) it is widely taught and understood and extremely widely applicable, and (2) each operator has a standard name that is widely explained.

> In contrast, most Haskell notation I've seen is either an ad-hoc invention for some library, or it is an ASCII version of notation in a niche domain like category theory.

But that's my point! When it was introduced, none of the symbology of school algebra satisfied condition (1) or (2); it was ad hoc (perhaps supported by some reasoning—as Reade's for the equal sign being two parallel lines, "than which no two things are more equal"—or perhaps not), and was found fully as abstruse and obfuscatory as you find symbolic operators in Haskell. Even Arabic numerals were thought a device for lies and deception, compared to good honest Roman numerals, when first introduced.) If we hadn't adopted those operators anyway, then we'd still be writing out all our equations in words as Tartaglia did, and our mathematics would be the poorer for it; had we stopped our notation earlier, we would still, as in pre-Arabic numeral times, send our children to special advanced schools to learn multiplication. New notation when first introduced is confusing and strange, but, once made commonplace, enables new thought, so that what was a niche domain becomes commonplace.

(For that matter, I'd disagree about (2). For example, there is a symbol called the vinculum that is used for many purposes in mathematics (https://en.wikipedia.org/wiki/Vinculum_(symbol)). Probably almost everyone has used that symbol for one of its meanings, but I'd argue that very likely almost no-one knows its name.)

I'm not claiming + and = have some underlying meaning, of course they were taught. My main point is that symbols are only truly useful when they are ubiquitous in their field. Before that, they are obscure and should be avoided.

If the Haskell community or the Lenses community wants to do the hard work of teaching everyone their new symbols, go right ahead. Until that work is done though, I would advice everyone to avoid using these symbols.

In regards to the vinculum, it's true that I've never heard that name, but I learned different names for the different uses - not in English though, as I took mathematics in Romanian, and some of the terminology and even notation is not the same (for example, repeated fractions are denoted using parentheses, not a vinculum - as in 1/30 = 0.0(3)).

> Before that, they are obscure and should be avoided.

Then how, if I might ask, is a symbol going to become ubiquitous, if it is not to be used while it is still obscure?

Not that you would necessarily use the same words in Haskell, but I'm curious how you would read `user1^.name` in Pascal, or `user1->name` in C or `(*user1).name` in C?

(Edit: I'd also be curious about `x += y` and `x << y` in C.)

user1^ is what the user1 pointer points to, if I remember my Pascal correctly. It's a structure and the .name says to take the name field of that structure. So ^. isn't a digraph, it's two separate operators.

Same with your C example. It's doing the exact same thing in C, except the operators (* and .) are separated from each other.

-> is a digraph. It means the same as `*.` I don't usually pronounce it, I just think of it as itself. If I have to say it to myself mentally, I say "sub". I'm not sure I've ever tried to say it aloud to a coworker; if I did, I might have said "arrow" or something.

You do have to learn these things, just like you have to learn all the other operators. But simiones still has a point - at least the math-based operators are much more widely known and understood than the category-based ones.

But what are their names? That was the standard applied to Haskell.. that the operators needed well understood names. C++ does not have that. Worse still, C++ operators are completely unintellible without context.

For example, '>>=' (commonly called bind in haskell) is well-specified. Anything I see it used with is going to be a Monad, and thus follow certain laws. Examining the imports, I can immediately tell what any operator is.

In C++? Forget about it. The 'left-shift' operator which is supposed to shift bits to the left, can somehow also print things to standard output. In what world can the terminal be bit-shifted left? In fact, we understand this because no one reads 'std::cout << "Hello world"' as 'shift std::cout left by "Hello world", because such a thing is non-sense, whereas '1 << 2' is '1 shifted two bits to the left'.

EDIT: And then, when you add in external libraries, it gets worse. `<<` can be used for creating lexers and parsers in boost if I recall correctly. Completely lawless, and, when you survey the ecosystem, also dangerous. So many bugs in C++ and such due to this.

I agree. I do feel there is a double standard here. C gets away with using `->` and `^=` and `*foo.bar` mixes prefix and infix and postfix with hard to remember precedence rules; C uses `{` and `}` instead of `BEGIN` and `END` and no one bats an eye. But when lens libraries use operators, suddenly some people lose their mind.

I perfer `rec^.field` with operators in lens for the same reason I prefer `ptr^.field` in Pascal over hypothetically writing `ptr DEREFERENCE ACCESS field` or `ACCESS(DEREFERENCE(ptr),field). It lets me hide what is essentially just plumbing-like-grammer behind operators in order to let me focus of the parts of the program related to the business logic at hand, namely `rec`, `ptr` and `field`. Otherwise the plumbing tends to drown out the more important parts of what is going on.

Note that the C and C++ standards have names for all of these operators. "->" is called the "member access" operator (though so is ".", to be fair). It also has the advantage of being a pretty clear typographic symbol - it can be called "arrow".

As for <iostream> overloading the left shift operator to use for writing to an ostream, that is widely regarded as a terrible idea, and such overloading is generally frowned upon in the community. In general, <iostream> was created before C++ was even standardized, and well before good practices became established, and doesn't reflect at all more common usage of the language (see also the fact that they don't throw exceptions by default).

The only other somewhat widely used example I know where operators are overloaded with entirely different meanings than their standard usage is indeed in the boost::spirit parser generator library, where they are trying to approximate the syntax of typical parser definitions using C++ operators, with at best mixed success. And they don't just overload <<, they overload all the operators with completely different meanings - such as overloading the unary plus (+x) operator to mean "token appears 1 or more times" and even the structure dereference operator (x) to mean "token appears 0 or more times" and so on. Still, I don't think too many people are crazy enough to mix boost::spirit with regular C++ in the same file.

Note also that operator overloading is not commonly supported in other C-family languages, typically because* they are trying to avoid C++'s usage of it.

What are their names? "Member access" is the name of "->". That's a pretty well-understood and easily-understood name.

> Worse still, C++ operators are completely unintellible without context.

Um, compared to what? The C++ operators require less context to understand than the Haskell lens operators by a large amount, so this seems like a really odd criticism.

Your last two paragraphs... yeah. Operator overloading has been used for some, uh, unusual uses, even by the standards committee.

I haven't used Pascal, so I can't comment on that.

user1->name : "user1 arrow name"

(*user1).name : "value of user1 dot name"

x += y : "x plus equals y" or "x increases by y"

x << y : "x left shifted by y"; the C++ overload for output streams is a terrible one that also doesn't have a name.

In Haskell, replacing the symbolic operations doesn't imply we have to abandon infix operations:

(and (eq (subtract u v) 10) (eq (mult u v) 1)) could be:

(u `subtract` v `eq` 10) `and` (u `mult` v `eq` 1)

> (and (eq (subtract u v) 10) (eq (mult u v) 1)) could be:

> (u `subtract` v `eq` 10) `and` (u `mult` v `eq` 1)

True. I used Lisp because I think it's reasonably common to use words for arithmetic operations in many Lisp dialects, but not in Haskell. But it's not the prefix notation to which I was objecting, but the idea of spelling out every operation when we've got a notation specifically designed to facilitate rapid thought and computation without that.