Closed robrix closed 9 years ago
I’m not at all sure this should actually be the same operator as normal function application. It works well enough for binary functions & combined with forward application:
let strings = [1, 2, 3] |> (map <| toString)
But it’s much less workable for ternary functions and chained with itself. This is because:
<|
is right-associative to enable use like so:
let digits = countElements <| toString <| 100
This operator ought to be left-associative because it applies to the rightmost, i.e. you want to write:
let sum = reduce <| (+) <| 0 <| [1, 2, 3]
to apply the rightmost parameter but you actually need to write:
let sum = ((reduce <| (+)) <| 0) <| [1, 2, 3]
Therefore, we need an operator for partially-apply-at-right (and at that point we may as well add partially-apply-at-left).
<||
and ||>
will be confusing for anyone used to F# where that operator is the analogue of <|
for binary functions.<<|
and |>>
might work; it looks sort of like “partially apply at the edge nearest the operator”/cc @dnalot
What about %|>
? You're only applying a part (a percentage) after all. :)
(I hope you are prepared for you to be the only one who can read clients of this operator family)
What about
%|>
? You're only applying a part (a percentage) after all. :)
That’s clever!
(I hope you are prepared for you to be the only one who can read clients of this operator family)
That’s one of the reasons why I’m interested in precedent. F# has <|
and |>
as analogues (unflipped and flipped respectively) of Haskell’s $
, and Prelude follows F# here (because $
isn’t a valid operator in Swift and because symmetry is nice).
This operator ought to be left-associative because it applies to the rightmost
I think I finally understand why F#'s |>
is left associative while $
is right (I think that's right)?
assert(f |> a |> b |> c == f(a)(b)(c)) // F# style
assert(f |> g |> h |> c == f(g (h (c)))) // Haskell syle
You're right they serve completely different purposes! F#'s is For applying parameters while Haskell's is for applying composition. That would mean F#'s <|
acts more like $
than its own |>
, lacking symmetry. I prefer Haskell semantics but couldn't mock up a version using reduce()
that played nicely with it either.
I originally mocked up an alternative currying splat/explode operator that works like:
let mapped = SequenceOf(numbers)
|> map* {$0.description}
|> String.join(", ")
But for it to work with reduce()
it should transform (S, U, F) -> U
to U -> F -> (S -> U)
(parens for emphasis) which doesn't seem very general (effectively curry but move the first parameter to the end for chaining):
let reduced = SequenceOf(numbers)
|> reduce* ([]) {(xs, x) in xs + [x.description]}
|> String.join(", ")
It also probably lacks sufficient precedence for Prelude.
That’s clever!
Maybe too clever :see_no_evil:
The cleanest solution I can imagine would be to make I'll keep thinking about it...|>
left associative like F# and rename the right associative version something reminiscent of $
.
N.b. the Optional
versions in my gist from @kristopherjohnson
Thinking further: I don't see the general utility of left associativity.
|>
is awesome at visualizing data flow.<|
is eliminating lispy parens piling up behind function callsassert(f |> g |> h |> c == f(g(h(c))))
I guess the utility I want is to treat Swift standard library functions like pipe-friendly curried functions. But I don't think we should pipe parameters into one another just to be functional :tm:. What about:
let reduced = SequenceOf(numbers)
// SEE: http://msdn.microsoft.com/en-us/library/ee370400.aspx
|> (reduce <|| ([], {(xs, x) in xs + [x.description]}))
|> String.join(", ")
It's not pretty but it's clean, more standard than my splatter, and feels more general (effectively curry all parameters after the first as one tuple e.g. maybe F#'s <|||
too).
I think you’re confusing the semantics of <|
and |>
. a <| b
applies a
to b
, i.e. a <| b == a(b)
. a |> b
applies b
to a
, i.e. a |> b == b(a)
.
Note also that F#’s <||
and <|||
aren’t doing partial function application; they’re (respectively) binary and ternary analogues of <|
. We don’t use those in Swift because Swift functions are all unary functions of tuples (i.e. they’re uncurried in the Haskell sense).
Relying via iPhone:
All my Haskell and F# comes from reading Scala academic papers but I'm pretty certain I have the semantics correct e.g. [1][2][3] etc.
But just like the links I agree with your design choice to mimic $
. What did you think about <||
? It exactly mirrors F# (except it also does partial application), works cleanly with reduce()
, and applies generally to any function.
Partial application is only a byproduct of Swift's higher order functions being uncurried. Without partial application we couldn't pipeline the sequence argument. AFAIK there's no formal operator describing partial application? My explode operator was kind of intended to explore currying as a solution without mucking with pipeline semantics.
I misunderstood the full semantics of <|
in F#. With some experimentation, I’ve learned that F#’s <|
can indeed take a binary function and does indeed only apply the leftmost operand. (Ditto |>
, which is effectively just flip (<|)
.)
let f x y = x + y
f 1 2
let g = f <| 1
g 2
Prelude’s <|
and |>
therefore behave identically to F#’s (modulo the right-associativity of <|
); and <||
, <|||
, ||>
, and |||>
aren’t currently available.
There’s still an important distinction between <|
/|>
and the operator which this branch is trying to define:
<|
and |>
apply the leftmost operand of (curried) functionsI’m going to experiment a bit to see how variations on this feel.
@dnalot Okay, I think I follow now. (I’m going to phrase a bunch of stuff you’ve already said in my own words now, just to make sure I’m on the same page.) There are two different uses of <|
and |>
in F#:
|>
<|
You can use |>
for partial application of e.g. map
, and you can use <|
to reveal data flow, but they’re suboptimal for those purposes:
|>
is left-associative so that x |> g |> f
is equivalent to f(g(x))
, but that makes toString |> [1, 2, 3] |> map
fail to compile<|
, meanwhile, is also left-associative. This means that f <| g <| x
is not equivalent to f(g(x))
, but map <| [1, 2, 3] <| toString
is equivalent to map([1, 2, 3], toString)
So, I propose:
<|
.<|
to left, matching F# and avoiding the need to parenthesize when chaining partial applications.flip
for ternary functions.Using flip
when you want to apply the rightmost parameter:
flip(map) <| toString
let sum: [Int] -> Int = flip(reduce) <| (+) <| 0
I’m not sure about the flip
overload; your suggestion of <||
may be a better option for reduce
here.<||
would still require ternary flip
, or we’d need a special operator to flip from the right, which I don’t really like the sounds of.
@dnalot I’ve implemented the above proposal :point_up:
Note in particular that I’ve opted to just use flip
instead of changing the operator’s semantics to work around Swift’s map
, reduce
, etc. being backwards.
Let me know what you think; and sorry for the confusion on my end!
@andymatuschak Same to you :smile: — would love to hear what you think.
I love your proposal and look forward to reading the diffs tonight! Although I wish there were a less noisy way to flip()
I appreciate the precedence. Your reply about pipeline use cases clarified the associative intent of those operators in a way the F# community hasn't captured :+1:
I’ve updated the README
to match, now. flip
does end up being noisy, but it’s not as bad as I feared: flip(reduce) <| (+) <| 0
isn’t so bad.
On the other hand, flip(flip(reduce) <| (+))
is pretty awful.
@dnalot I’m not going to merge this in for a bit yet (I want to mull it over and collect more feedback), so take your time :+1:
/cc @patrickt who knows things™
Okay, I think I’m pretty much content with this. Going to let it sit for a little longer and then merge.
:+1: I love how Prelude et.al. fill a gap cleanly between the scopes of LlamaKit and swiftz while addressing practical usage!
:sparkles:
Fixes #19.