In general I avoid all custom operators and only use operators that are in packages preinstalled by the compiler (basically just base and containers).
But I'm not a hardliner. I do use backticks sometimes when building joins with Esqueleto and I do use a limited set of lens operators, like ^. and sometimes the %= variants if the situation calls for it.
For the former assertion: ^. means "get a single result". ^.. means "get multiple results". ^? means "get zero or one result". ^@.. means "get multiple results, along with their indices". <<|>~ means "modify a value by combining the target with the |> operator from Snoc, then return a tuple of the old target value and the full structure including the combined value". There is a tiny language in the pattern of operator names, and it's worth the 3 minutes of work it takes to learn it.
And as a reward for learning it, you get to write expressions with far fewer parentheses. This is a massive win. Parenthesized expressions introduce a miserable minigame during reading, where you have to properly match each paren to its correct partner keeping a mental stack to handle nesting. By contrast, the lens operators give you the far simpler mental parsing task of separating the optic, the input, and the operation on the input. There's no nesting involved. The process is a simple visual scan that doesn't require keeping a mental stack. It's a lot easier to quickly read and comprehend.
About the only thing you lose is the ability to easily read code out loud. I don't limit myself to thinking in sounds, but I guess for some people it's important to communicate code out loud. For those kinds of pedagogical purposes, I guess it's ok to pass on the operators. But for code I'm going to work with over a long period of time I'd much rather have the readability advantages of the operators.
I find heavily parenthesized expressions easy to read, just because I tend to break them into multiple lines and the indentation serves as a guide. Don't put too many of them on a single line.
As a result, you just don't think about precedence when reading code. If you assume the code type checked correctly, you know that it all just makes sense. You don't need to create a parse tree. You just trust.
(Actually, this is the huge advantage of Haskell in most every case. You don't need to understand everything. You just trust that it does what makes sense, and you're right. The compiler enforces it.)
Per Kmett’s original talk/video on the subject, I can confirm my brain shifted pretty quickly to look at them like OOP field accessors. And for the three above, the mnemonics are effective:
“^.” is like an upside down “v” for view.
“.~” looks like a backwards “s” for setters.
“~%” has an tilde so it’s a type of setter and “%” has a circle over a circle, so it’s over.
I’ll also add that my experience in recent versions of PureScript things get even nicer: visible type application lets you define record accessors on the fly like:
foo ^. ln@“bar” <<< ln@“baz”
“.” Is unfortunately a restricted character and is not the composition operator like Haskell, but I alias “<<<“ with “..”
The pretty obvious question with the above is: why don’t you just write “foo.bar.baz”. In my case I use a framework that uses passed lenses for IoC, but I think “%~” is always nicer and less repetitive than the built-in alternative.
Sure, a Haskell programmer does write toList when relevant. The lens library also has operators named each or over or _Left.
But let's say I write <$> and you tell me to use fmap. Is that any better? Not particularly. If you didn't know what Functor is, you wouldn't automatically be able to infer it by reading the name `fmap`.
A large number of Haskell concepts simply have no analogue in everyday English.
But I think my point still stands? What is the 'normal' way to write ^. for example?
I find it to be a lot more comprehensible and transparent than the Haskell version.
https://www.stackage.org/haddock/lts-23.27/base-4.19.2.0/Dat...
https://hackage-content.haskell.org/package/bluefin-0.0.16.0...
[1]: I'm not even exaggerating - https://hackage-content.haskell.org/package/lens-5.3.5/docs/...
The easiest example would be something like wrapping a bunch of arithmetic operations with a "cumulative" monad. Effectively this changes your add, sub, mul, div functions such that instead of taking 2 floats and returning a float, they take a hashmap and return a hashmap. The hashmap consists of the original args as well as the cumulative total, for whatever reason. The details of the hashmap are hidden from you, you use the functions as per normal.
You could also make the wrapper monad have some state, and then batch the operations while making them appear to execute sequentially, or make it appear you are doing pure logic when I/O is happening under the hood.
While you can do monads in dynamic languages, it can be hard to reason about changes to the code without strong compiler support, so typically you see it more often implemented in statically typed languages.
In dynamic languages such as lisp you might be better off writing a small interpreter, and in OO languages there are other patterns that might serve the purpose better.
I still don't know what a monoid is though. Or an applicative.
In short, it's a data type with addition defined. So
"a" + "b" = "ab" <-- string and concat is a monoid
1 + 5 = 6 <-- nat and natural number addition is a monoid
[1] + [2] = [1, 2] <-- list and concat is a monoid
*edit* (it also has a zero defined, such as "" or 0 or [])
> Or an applicative
you can think of these as "i'd use a functor here via fmap, except my mapping function takes more than one argument"
A monad is a flatmappable.
A monad is a flatmappable.
A monad is a flatmappable.
moomin•7mo ago
xtoilette•7mo ago
neanderzander•7mo ago
https://academy.fpblock.com/haskell/tutorial/lens/
wk_end•7mo ago
https://gcanti.github.io/monocle-ts/
raluk•7mo ago
cosmic_quanta•7mo ago
Thank you for sharing
KPGv2•7mo ago
raluk•7mo ago
ohdeargodno•7mo ago
Kotlin's Arrow library hits a good middle ground between FP wizardry and readability, and their documentation on lenses are understandable for the average person: https://arrow-kt.io/learn/immutable-data/lens/ / https://arrow-kt.io/learn/immutable-data/intro/
epgui•7mo ago
Uhhh... Haskell syntax is simpler than python's or javascript's. It's neither obscure nor impenetrable, but it sounds like it's different than what you're used to.
HappMacDonald•7mo ago
epgui•7mo ago
HappMacDonald•7mo ago
-- | Flipped version of '<$>'.
infixl 1 <&>
(<&>) :: Functor f => f a -> (a -> b) -> f b
as <&> f = f <$> as
```
"infix", "Functor", and "as" are the only words in this code. Everything else is single letters (thanks math traditions..) and punctuation. What's a <&>? <$>? ::? We've got two different kinds of arrows, => and ->. -- is obviously enough a line comment. At least I know what = means.. give or take it's constant ambiguous meaning between languages of assignment and/or equality testing.
And this isn't even delving into the black arts of defining types, where the really ugly punctuation toolkits get opened.
I don't care whether or not they represent regular functions nor what their calling syntax is. What I care is that the base language has many many dozens of them to remember and then to parse in the wild, and then that authors are encouraged to continue proliferating more of them:
```Haskell
-- What does this 'mouse operator' mean? :thinking_suicide:
(~@@^>) :: Functor f => (a -> b) -> (a -> c -> d) -> (b -> f c) -> a -> f d
```
Credit: Kowainik's Haskell Style Guide https://kowainik.github.io/posts/2019-02-06-style-guide
epgui•7mo ago
HappMacDonald•7mo ago
epgui•7mo ago
deadfoxygrandpa•7mo ago
in fact the example you picked is trivially simple even if you dont really know the syntax. all you need to do is not rage out and stop thinking. it's literally saying that "as <&> f" is equal to "f <$> as". so you dont even need to know what <$> is, its extremely straightforward that <&> is just flipping the order of the arguments to <$>
also if you read the type signature then there really arent many possible things this function could do. you start with a Functor a and a function that maps from a to b, and you end up with a Functor b. there's really only one possible implementation of that (applying the function (a -> b) to the a in Functor a)
like im curious, what would you even name these things to make it clearer? it's so abstract in generic in the very concept of it that it's hard to come up with any more specific naming
Functor functor => functor someType -> (someType -> someOtherType) -> functor someOtherType
is that better?
HappMacDonald•7mo ago
But what is a <$>? Should one google something like that and hope that Google honors it like a word instead of ignoring it like it usually does punctuation? (In this case Google does appear to honor it as a keyword.. in 9/10 circumstances with punctuation blocks it however does not) Or ask an LLM and get a confidently wrong answer there?
Is it even one piece of punctuation, or two or thee pieces placed adjacent to one another? Like maybe the <> are like parenthesis and the $ is an argument in there.. who knows until you study the nuts and bolts of how this particular language lexes things. (I'm just kidding though.. I get that Haskell would never never be so cruel as to use <> as parenthesis anywhere ;)
Any keyboard bash of punctuation can be defended as understandable once you've memorized all of the symbols and composition rules to parse it. People who code Brainfuck all of the time can read it quite readily, and might even argue that it's overblown that it was ever named "Brainfuck" to begin with.
> Functor functor => functor someType -> (someType -> someOtherType) -> functor someOtherType > is that better?
Moderately, though there remains room for improvement.
Personally I'm able to follow what's happening to the right of the fat arrow because I've cut my teeth on that style of type signature in Elm and Lean. It's taking as first argument a functor over one type ("typeA" might be a still better name), second argument is a function that in turn accepts typeA and emits typeB, and this signature's output is a functor over typeB.
Unfortunately I've mostly forgotten what a functor was after once learning it (IIRC it's an isomorphism in category theory or some kind or another? I can remember what monads are and how to use them in coding, and a monad over a type makes sense to me.. it's basically a tagged type) and in that light the left side of fat arrow being "Functor functor" doesn't clarify much. the lowercase "functor" (or "f") is obviously like a type variable, though probably specifically for functors instead of types. The uppercase one.. I don't know.. says something about the shape of the operator being declared?
In any event, finding a way to make the variable name descriptive without echoing the keyword at the beginning would be helpful, just as in prose we often use synonyms to avoid confusing word re-use.
And finally: bear in mind this isn't primarily a discussion about "I lack exposure to X therefore fill me in on X", but instead about "lots of people lack exposure to X therefore being easier to digest in the first place would have been preferable".
epgui•7mo ago
No... that's what Hoogle is for. You can literally search by symbol or by type signature, and it's amazing.
Examples
- By symbol: https://hoogle.haskell.org/?hoogle=%3C%26%3E
- By signature: https://hoogle.haskell.org/?hoogle=Functor%20f%20%3D%3E%20f%...
ohdeargodno•7mo ago
It doesn't make it a bad syntax. It is, however, objectively terrible for anyone unfamiliar with it.
epgui•7mo ago
It’s not bait. Call me pedantic if you want, but I think words have meaning.
pxeger1•7mo ago
KPGv2•7mo ago
smegma2•7mo ago