[swift-evolution] Changing the curried static methods
Brandon Williams
mbw234 at gmail.com
Sun Mar 13 17:26:46 CDT 2016
No problem Erica, I find this to be pretty subtle!
That's definitely how static versions of instance methods work today, and
it does it's job pretty well! My suggestion to flip the arguments is a bid
to increase the readability and re-usability of these methods without
changing any of the real semantics.
Here's how we can do it manually in order to see the benefits. The
following function:
func flip <A, B, C> (f: A -> B -> C) -> B -> A -> C {
return { b in { a in f(a)(b) } }
}
simply converts `A -> B -> C` to `B -> A -> C`, i.e. flips its arguments.
Let's use it make a version of `UIView.removeFromSuperview` that I think is
more understandable:
extension UIView {
@nonobjc static let _removeFromSuperview =
flip(UIView.removeFromSuperview)
}
(ignore that @nonobjc, it's only necessary cause Swift is trying to
generate a dynamic accessor)
With this method your code example becomes
v.subviews.forEach(UIView._removeFromSuperview())
To me this reads: for each subview of `v`, apply the action
`UIView._removeFromSuperview()`. Compare this to the equivalent with
today's static method:
v.subviews.map(UIView.removeFromSuperview).forEach{ $0() }
This is read as: map the subviews of `v` into an array of actions, and then
invoke those actions.
The benefits of this flip are easier to see with different examples,
because I agree with Stephen that it's hard to beat a simple
`v.subviews.forEach { $0.removeSuperView() }` in this particular example.
If static methods had their arguments flipped, we'd be able to easily
construct functions like `CGRect.insetBy(10.0, 10.0)` and
`CGRect.offsetBy(-20.0, 10.0)`. These are now free functions without any
mention to a specific rectangle. We could compose them to get a function
that simulataneously insets and translates, all without mentioning a
rectangle. With today's static methods we'd have to apply `flip` to each
one, i.e. `flip(CGRect.insetBy)(10.0, 10.0)`.
This becomes very powerful with constructing data pipelines that describe
how to transform data without ever actually mentioning the data. There are
quite a few examples of things like this in Cocoa. A nice one is Core
Image, in which you can build a pipeline of filters that you can feed
images into.
On Sun, Mar 13, 2016 at 3:45 PM Erica Sadun <erica at ericasadun.com> wrote:
> I'm probably missing the point here, so apologize in advance. Instead of
> reducing a function with an n-arity set of arguments to a partially applied
> function with (m|m<n)-arity set of arguments, it's building a functional
> application and applying that to an arbitrary receiver.
>
> `UIView.removeFromSuperview(receiver) `
>
> defines
>
> `receiver.removeFromSuperview`
>
> which can then be applied with whatever arguments, () in this case.
>
> `UIView.removeFromSuperview(receiver)()` aka
> `receiver.removeFromSuperview()`
>
> With mapping, you can do:
>
> let v = UIView()
> (1...5).forEach{_ in v.addSubview(UIView())}
>
> // You can apply each subview as a receiver, returning a function
> let applied = v.subviews.map(UIView.removeFromSuperview)
> // [(Function), (Function), (Function), (Function), (Function)]
>
> // And then you an execute them in forEach
> print(v.subviews.count) // 5
> v.subviews.map(UIView.removeFromSuperview).forEach{$0()}
> print(v.subviews.count) // 0
>
> The whole map/forEach could be defined down to apply in a possible
> language extension, as I mentioned before.
>
> -- E, who apologizes for really not getting this
>
>
> On Mar 13, 2016, at 1:21 PM, Brandon Williams <mbw234 at gmail.com> wrote:
>
> I think this highlights some of the confusion around the current curried
> convention. Void methods are curried as A -> () -> (), which means you
> would use it like:
>
> UIView.removeFromSuperview # => UIView -> () -> ()
> UIView.removeFromSuperview(view) # => () -> ()
> UIView.removeFromSuperview(view)() #> ()
>
> I find this confusing because removeFromSuperview reads as an action, yet
> UIView.removeFromSuperview(view) does not perform the action but rather is
> an action itself that requires a further invocation ().
>
> With arguments flipped we would have:
>
> UIView.removeFromSuperview # => () -> UIView -> ()
> UIView.removeFromSuperview() # => UIView -> ()
> UIView.removeFromSuperview()(view) #> ()
>
> It now reads to me that UIView.removeFromSuperview() is the action that
> will do the removing, and UIView.removeFromSuperview()(view) is applying
> the action to a view.
>
> I don’t advocate using removeFromSuperview in this manner, but if one were
> to I believe the latter convention is easier to reason about without having
> to look up types in a playground (as I had to do a few times to write this
> :P)
>
>
> On Sun, Mar 13, 2016 at 1:50 PM Erica Sadun via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>>
>> > On Mar 13, 2016, at 11:30 AM, Stephen Celis <stephen.celis at gmail.com>
>> wrote:
>> >
>> >> On Mar 13, 2016, at 1:18 PM, Erica Sadun <erica at ericasadun.com> wrote:
>> >>
>> >> Since removeFromSuperview doesn't take a UIView argument, it sounds
>> like what you're looking for is
>> >> something that acts like "apply", to apply a
>> lambda/closure/selector/whatever to each member of a collection.
>> >>
>> >> view.subviews.apply(UIView.removeFromSuperview)
>> >>
>> >> -- E
>> >
>> > This is what `forEach` currently does with the existing curried static
>> syntax, right?
>> >
>> > I was more interested in the implications of an example brought up in
>> the OP:
>> >
>> > frames.map(CGRect.insetBy(-10, -10))
>> >
>> > - Stephen
>>
>> forEach currently does f(x).
>> apply would do x.f()
>>
>> -- E
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160313/830efcc4/attachment.html>
More information about the swift-evolution
mailing list