Haskell `$` in F# possible?
Asked Answered
D

1

6

In Haskell, we can write

print $ abs $ 3 - 5

using $ .

In F#, we can write

printfn "%d" << abs <| 3 - 5

However, in many cases in F#, it would be also useful to have the same functionality of $ since the above are simply expressions with binary operators.

The trick of Haskell $ is that has somewhat the lowest precedence among its binary operators.

So, I investigated again

https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/#operator-precedence

and I observe, unfortunately, that there are no operators in lower precedences that can overload safely because all of them seem essential.

Do you have any ideas on this?

Do you think Haskell $ in F# is possible??

Drifter answered 7/9, 2022 at 19:14 Comment(10)
Not sure what your ultimate goal is here, so I ask: You do know about interpolated strings? – Liebknecht
Hi, it's not about the ultimate goal thing, as I mentioned it's simply about expressions with binary operators. Sure I know the stuff, but not sure why you mentioned it. – Drifter
FYI it's not idiomatic F# to use << or <| (I work primarily in F# and I don't use them at all). You can always express things going "forwards" with >> and |>. It's a very intentional stylistic difference. This example would become 3 - 5 |> abs |> printfn "%d". Something to bear in mind if you plan to write F# for others to read. In this particular example I would simply add parens printfn "%d" (abs (3 - 5)). Perfectly non-clever! 😁 – Collectivity
I mentioned it (interpolated strings) because I wasn't sure if this was an XY problem. Interpolated strings in F# is relatively new, so there's much documentation that doesn't mention it. – Liebknecht
HI @Collectivity Come to think of it, that totally makes sense. Thanks for sharing the idiomatic view. – Drifter
@BentTranberg Probably the reason you mention interpolated string is that the symbol of $ is used there. The Haskel binary operator symbol $ has no relation to it. – Drifter
No, that's not why. I explained why I mentioned it in my previous comment. – Liebknecht
@BentTranberg OK, now I see I let you misunderstand due to my sample code using printfn. Which is actually purely on purpose for printing the result of the implementation of the binary operator that is the target of this QA. – Drifter
I misunderstood nothing. Again I refer to my explanation as to why I mentioned it. – Liebknecht
Then I would say "misleading by my Question" because this is not XY problem and Interpolated strings in F# is nothing to do with the expression of binary operators. – Drifter
C
9

Like Haskell, Fβ™― also allows you to define new infix operators, not just overload existing ones. Unlike Haskell however, you can't select precedence (aka fixity) at will, nor select any non-letter Unicode symbol. Instead, you must choose from !, $, %, &, *, +, -, ., /, <, =, >, ?, @, ^, |. The fixity is then determined by analogy with the standard operators, in a way I don't entirely understand but what does seem to hold is that for any single-symbol infix, you can make a custom one with the same fixity by adding a . in front.

So, to get a lowest-fixity operator you'd have to call it .|. However, | is left-associative, so you couldn't write printfn "%d" .| abs .| 3 - 5. However I'd note that in Haskell, your example would also rather be written print . abs $ 3 - 5, and that can indeed be expressed in Fβ™―:

let (.|) f x = f x
printfn "%d" << abs .| 3 - 5

To transliterate print $ abs $ 3 - 5, you'd need a right-associative operator. The lowest-precedence custom right-associative operator I can manage to define is .^, which indeed gets the example working:

let (.^) f x = f x
printfn "%d" .^ abs .^ 3 - 5

However, this operator doesn't have very low precedence, in particular it actually has higher precedence than the composition operators!


Really, you shouldn't be doing any of this and instead just use printfn "%d" << abs <| 3 - 5 as you originally suggested. It's standard, and it also corresponds to the preferred style in Haskell.

Cache answered 7/9, 2022 at 19:52 Comment(5)
On right- vs left-associativity: I and many others consider the right-associativity of $ to be a mistake in the first place, so it's not obvious that .| being left associative is actually a bad thing. Generally f $ g $ h $ x can be written f . g . h $ x, so changing the associativity wouldn't really lose anything, and you would gain the ability to write things like f (g x) (h y) as f $ g x $ h y whereas currently there's no good, parenthesis-free way to express that. – Thinia
@DanielWagner yeah-ish. As I said, f . g . h $ x is normally preferred (or f . g $ h x), but there are also things that can't be written this way, such as max <*> sqrt $ negate $ pi - 3, which would require extra parentheses: (max <*> sqrt) . negate $ pi - 3. The reason that this seldom arises in practice is that it only happens with infix expression that have a function type, which usually means either . or the function applicative / function monad (which I also consider dubious style). Anyway you can't really make the point that changing the associativity wouldn't lose anything. – Cache
ap max sqrt . negate $ pi - 3... But yes, I take your point. I would say that what you lose is much more rare than what you gain. I don't remember ever seeing something like what you describe. – Thinia
Thanks for your versatile answer and for taking your time! I've tried a lot of things reading your answer and I reached the exact consequences. The official document is not complete I feel because of lots of unknown behavior, especially I don't understand why only .^ or ^^ will be accepted for the custom right associative. I really appreciated again. – Drifter
@DanielWagner Thanks. It's a very interesting discussion, however still it seems identical to write printfn "%d" << abs <| 3 - 5 – Drifter

© 2022 - 2024 β€” McMap. All rights reserved.