> I'm curious what the type signature of the splat operator would be?
None of the options I present actually involves a standalone operator;
even 3 is a magic syntax which happens to look like an operator. That's why
you can use the splat without specifying a tuple to get a tuple-taking
version of the function.
Ah, I see, at least for #1. I'm assuming that to get the function that
takes a tuple, you'd have to explicitly use the parameters: overload:
let f = concatenate // Gives you concatenate(_, to:), type signature
(Int, to: String) -> String
f(1, to: "foo") // Legal
f(2, "foo") // Legal, argument labels may be omitted
f(3, from: "foo") // Illegal, argument labels don't line up
let g = concatenate(parameters:) // Gives you concatenate(parameters), type
signature (Int, String) -> String
g(1, to: "foo") // Legal, tuple label ignored
g(2, "foo") // Legal
g(3, bar: "foo") // Legal, tuple label ignored
For #2, I'm asking what the type is of .apply(to:)?
let f = concatenate(_:to:).apply(to:) // I assume this has type (Int,
String) -> String
var f = concatenate
var g = f.apply(to:) // Is this legal? What's the type?
class Promise<ResultTuple> {
init(resolve: ResultTuple -> Void) {
self.resolve = resolve
}
func onPromiseComplete(result: ResultTuple) {
resolve.apply(to: result) // Is this legal?
}
let resolve: ResultTuple -> Void
}
let p1 = Promise(resolve: concatenate) // Presumably this is a
Promise<(Int, String)>
let p2 = Promise(resolve: f) // What's the type of this? Compiler smart
enough to figure out it's Promise<(Int, String)>?
let p3 = Promise(resolve: g) // How about this?
let p4 = Promise(resolve: Promise(resolve:)) // For extra recursion...
// This type signature is wrong, g takes an arbitrary argument list...but
how do I write that?
func compose<Params, Intermediate, Result>(f: Intermediate -> Result , g:
Params -> Intermediate) -> Result {
return { args in f.apply(to: g.apply(to: args)) }
}
// Here's a function that takes an Array of functions and applies them one
after another...
// I don't really know where to start with this...the types of all of its
arguments seem inexpressible
func composeMany</* ...arbitrary many intermediate params... */>(functions:
[/* What type for arbitrary function? */]) {
reduce(compose, functions, identity)
}
A lot of this is going overboard with functional programming and is
probably outside the scope of what Swift's designers want it to
accomplish. My point isn't to turn Swift into Haskell, it's to point out
some of the potential edge cases when interacting with other language
features. Many of those edge cases can be avoided by saying "This is a
simple syntactic feature, it *looks* like this but all it does is construct
a closure that's syntactic sugar for applying this function to each of the
components of its tuple-based argument." But then if it looks like a
method, people are going to wonder why they can't pass it to a function or
store it in a data-structure.
I *did* consider adding a fourth option, that of annotating parameters as
converting n-ary functions to tuple-taking equivalents, but I didn't feel
like I had a good enough idea of how that would work to really suggest it.
Roughly, an `apply(_:to:)` function would look something like this:
func apply<In, Out>(function: @splatting In -> Out, to params: In)
-> Out {
// `function` is of type `In -> Out`, where In is a tuple
containing the types
// of the parameters of the function that was passed.
Essentially, the generic system
// implicitly wraps the function in a trampoline that
takes a tuple of compatible arguments.
return function(params)
}
But this starts getting deep into type checker and generic system
territory, which I don't understand well enough to feel comfortable
proposing.
> If it can't, I have a strong preference against the options (#1 & #2)
that look like normal function call syntax. Because you won't be able to
do several things that you're accustomed to with functions: assign them to
variables, store them in containers, pass them as parameters to other
functions.
I think this feature has to be able to make a first-class, tuple-taking
closure, and all three alternatives I presented are intended to do that.
That's what the second example for each syntax (the one where I map it over
an array of tuples) is meant to show.
This is less of a problem with explicit language syntax, because you
could have a rule in the typechecker that says "If the expression being
splatted is a tuple of type (A, B, x: C), then it must be applied to a
function of type (A, B, C) -> Ret, with the result type of the call being
Ret." I think you can also get around many of the type inference pitfalls
as well, because in most cases the types of the function and tuple are
unlikely to need inferring (occasionally this will require explicit type
annotations on tuple components, but it seems like most of the time they
will have already been inferred when the tuple was declared).
As long as the splatting is explicit—that is, the parser can tell whether
you're making a normal call or a tuple call—I don't think the overload
resolution on a splatted version of a function is any more difficult than
the non-splatted version. Possibly even easier, depending on how we handle
default arguments.
> And it's much easier to do "partial splat" operations, where, for
example, you may want to pass the first couple arguments of a function
explicitly but splat the rest.
I haven't really considered how to do this kind of thing, but I think it's
probably better represented by constructing a single tuple containing all
of the arguments. I believe there was another thread recently that
discussed tuple combining operators.
I saw that, briefly. I hope that something like that makes it in, but it
has similar issues with "What's the type signature of this operator, and if
the only way it can exist is as compiler-supported syntax, how do we make
it clear to users that this is a compiler language feature and not a
first-class function call?" IIRC the syntax suggested there made use of
the #thisIsACompilerDirective naming convention. I wonder if that might be
appropriate here:
f(#splat(tuple))
concatenate(_: #splat)
The latter form also suggests a way this could be used for partial
application:
concatenate(2, #splat) returns a closure where the first argument is always
2, but any remaining arguments are pulled from the provided tuple.
···
On Wed, Feb 10, 2016 at 5:41 PM, Brent Royal-Gordon <brent@architechies.com> wrote: