robrix / Prelude

Swift µframework of simple functional programming tools
MIT License
409 stars 26 forks source link

Low precedence right associative application (Haskell `$`) #55

Closed werediver closed 7 years ago

werediver commented 7 years ago

Status quo

By analogy with F# (see F# Symbol and Operator Reference) the backward pipe <| operator in this library has low precedence and right associativity. This kind of function application operator is of limited usefulness, because it doesn't allow consecutive application without extra parentheses.

Compare:

> List.sum (List.take 2 (1 :: [2; 3])) ;;
val it : int = 3
> List.sum <| (List.take 2 <| 1 :: [2; 3]) ;;
val it : int = 3
> let inline (^<|) f a = f a ;;
> List.sum ^<| List.take 2 ^<| 1 :: [2; 3] ;;
val it : int = 3
> sum (take 2 (1 : [2, 3]))
3
> sum $ take 2 $ 1 : [2, 3]
3

Notice that the right associative function application operator allows to drop the maximum possible number of parentheses (often all).

Proposal

Lets introduce a low precedence right associative function application operator to allow more concise and expressive code!

How to name it?

F# ^<| intuitively connects with the left associative variant (<|), but originally it is a high precedence operator.

Haskell $ is very concise and defines exactly the low precedence right associative function application operator, thus it seems to be the best choice.

nikita-leonov commented 7 years ago

@werediver name is still an open question. Both ^<| and $ are not allowed in Swift — Custom operators can contain any of the following ASCII characters /, =, -, +, !, *, %, <, >, &, |, , or ~, or any of the Unicode characters in the “Math Symbols” character set. http://nshipster.com/swift-operators/

nikita-leonov commented 7 years ago

@robrix, can you take a look? I see "brackets hell" often due to a current state of <|. It complicates code reading, as well as create compile time errors that sometimes hard to debug. Having an operator suggested above will be helpful.

werediver commented 7 years ago

Actually, I'd prefer to change the declaration of <| to make it right associative. The only, but possibly an important, issue here is compatibility with existing code.

Do you guys think this change can lead to bugs in existing code? I think it will more likely make some code incorrect, what will be easy to fix.

robrix commented 7 years ago

I’d be in favour of changing the associativity rather than introducing a new operator.

Changing the associativity may cause build failures, but I don’t think it’d be likely to cause bugs, because the types wouldn’t line up. Consider:

(f <| g) <| h

We can see here that f : ((A) -> B) -> C, g : A, and h : B. Contrast with:

f <| (g <| h)

Here, f : (A) -> B, g : (C) -> A, and h : C.

That said, overloading and parametricity might throw a spanner in the works, or at least make for confusing errors. Even so I think fixing the associativity is probably the right choice.

nikita-leonov commented 7 years ago

@werediver can you provide a PR with a change?

werediver commented 7 years ago

@nikita-leonov yes, within 24h.