I'm not sure this is a good idea. I also use a >>> composition operator and I would't want that to have a lower precedence than |>. I would expect to be able to pipe a value into a composed function without having to use parentheses.
My advice is to steer clear of the dot-syntax. Leave it as a note to consider in the Future Directions section. Otherwise, the core of the pitch is at serious risk of losing momentum.
I did not analyze all uses of ., but related to member functions I feel my argument was pretty sound. I'm not an expert though. So, if I made no sense, please show me where my argument goes off the rails.
I think if we're going to add a functional operator we need to consider what precedence relationships people would want if they build on that and take it into consideration. We can probably learn from prior art in other languages that provide these operators.
Certainly. We have the advantage that Swift no longer requires operators to have precedence relative to every other operator. This allows the functional operators to have precedence defined only relative to each other; we can require parentheses when they are mixed, for example, with arithmetic operators. This greatly narrows the task of defining appropriate precedence relationships.
I feel that is (at the moment) the core of my pitch. With as an (I think very interesting) alternative adding an apply method to every type, but then we first have to decide if we want to be able to have extensions for unconstrained generics.
I'm not sure that, a priori, I would expect this to be (5 + 5) |> takesInt rather than 5 + (5 |> takesInt). Certainly, . does not work like this: 5 + 5.frobnicate() is not equivalent to (5 + 5).frobnicate(); personally, I'd expect that |> would work similarly to .
What Swift allows us to do is to avoid debating whose expectation is more reasonable, and to ensure that the order of operations is crystal clear at the point of use.
I think apply is a great concept and that a distinct operator like, \> or |> or whatever, would be a nice complement to it. All that is do-able in a single proposal. Although, bike shedding of the operator will tend to generate extra discussion and controversy that will take away from the base concept.
I think that trying to develop a streamlined syntax for chaining that would implicate a change to the meaning of dot-syntax is a very heavy lift.
I offer these opinions as advice to whomever may decide to write a proposal on this subject.
And, to put a finer point on it, what I understand you to be suggesting is that apply would be a special pseudo-member name and that, since it is special, we might elide the name when using dot-syntax. Eliding member names from the right-hand side of a dot would be something entirely new. IMO, signaling the elision with a parentheses or braces would not help much.
Let me flip it around: If, in all other cases on the right-hand side of the dot, we must write the name of the member that we are accessing, why should it be any different for apply? It is just five letters, and nicely conveys the intent.
To do this, we need to be able to write an extension that adds apply to all types. I don't have an opinion on whether this is a good idea or not, but I read somewhere that Swift might never allow this. I think one of the arguments against it is that it pollutes every type, so I'm trying to find a solution that does not conflict with user defined methods.
If we decide that extensions on an unconstrained generic type is a good idea, then I'm all for just adding 'apply' to every type. For the moment, under the assumption we will not get this feature, I'm trying to explore the space we have outside of this.
Would it be more clear if we add an argument label?
Note: What is proposed here as 'apply' is more like Kotlin's 'let'. We might want to consider using a different name and leave room for a future 'apply' that is more similar to Kotlin's 'apply' (if possible with Swift's mutation model). I don't really like the name let (although perhaps I should just get used to it). Perhaps 'into' could be a good name.
Unconstrained generic type extensions or not?
The last example comes very close to what we'd get with extensions on unconstrained generics. Just change the different argument labels into multiple function names and remove the colon on the call site.
Some questions I think need to be explored are:
Do we want to be able to create extensions that apply to all types?
Do we want to make that available for everyone, or only to the standard library?
If we can do this, do we add regular methods like apply, also, etc., or do we use a nameless method in order to prevent conflicts with user defined methods?
Will we get support for a label for the first trailing closure? This makes the nameless method with argument labels option a reasonable alternative to regular methods. Otherwise I'm not sure it will be worth it.
Do we need variations, or is just the originally proposed apply enough?
That's not necessarily true. It might be possible to add a member to every type with compiler support. The only member I know of this kind is .self, that simply returns the instance itself.
The fear of namespace pollution is irrational if the member is completely generic and, by nature, applicable to every type.
Yes, you are correct. I should've been more clear. I was trying to describe what kind of functionality we need. Not how this would be implemented.
I can imagine that some people don't like having member functions they didn't ask for. Especially if they have defined their own apply methods. I assume that overload resolution will always favor a user defined method over the completely unconstrained one, but it's still pollution. I really don't have a strong opinion on it, but I'm sure some people will.
Let's say apply(…) was added as an implicit member, and I have a type which needs its own apply method, what would the implications be?
Would I have to escape myapply using backticks, not only at its declaration but also at every call site?
(As seems to be the case with self et al.)
struct S {
typealias `Type` = Int // <-- Have use backticks here.
static var `self`: Int { 123 } // <-- And here.
var `self`: Int { 456 } // <-- ...
static func `init`() -> Self { Self() } // <-- …
}
let _: S.`Type` = 123 // <-- …
let _: Int = S.`self` // <-- …
let s = S.`init`() // <-- …
let _ = s.`self` // <-- …
If this apply method is deemed worthy of being added as an implicit member, on par with self, then what other methods would also qualify?
IMO, ideally, function application is an example of something that should be very straight forward, something that just falls naturally out of a language with first class functions, and here we're talking about all sorts of special casing and compiler support. : /
(Of course, it can be relatively straight forward in Swift, ie if only using free functions and operators, but that's not how most people write Swift, and IDE's don't have code completion in the form of applicable free functions etc.)
For source compatibility reasons, you would have to be able to specify your own apply method, and the user-defined apply method must always be preferred over the implicit member, except in cases where it is not possible today to invoke it. An example of this second case would be:
struct S {
func apply<U>(_ transform: (Self) -> U) -> U {
return transform(self)
}
}
func f<T, U>(_ t: T, _ transform: (T) -> U) -> U{
return t.apply(transform) // This was invalid before, so it's ok that `t.apply` refers to the implicit member
}
I guess the custom apply wouldn't need backticks and would be preferred over the standard implicit member: it seems that this would be 100% source compatible. I wouldn't treat apply as a special keyword.
We're talking about a lot of things. Personally, I'm just enumerating options and highlighting what would need compiler support to be usable and ergonomic.
This should probably be on top, written in all caps. The path taken by Swift, the tools and most of the community favors instance methods over free functions and operators. As the Pointfree guys frequently point out, free functions compose, methods don't: this implies that it's hard to come up with natural and straight forward solutions even for something as simple as reverse function application.