Splat


(Brent Royal-Gordon) #1

So, since SE-0029 has been accepted, let's think about explicit replacements. So far, I've been able to think of three general approaches. For the examples below, I'll assume there's a `func concatenate(number: Int, to string: String) -> String`, which does the obvious thing. Where supported, I will fully qualify names with SE-0021 syntax, but in some cases this might not be necessary.

    1. Special parameter label.

`concatenate` is implicitly overloaded with a `func concatenate(parameters: (Int, String)) -> String`.

  concatenate(parameters: tuple)
  tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument label.
- Not clear how it would distinguish between `concatenate(_:to:)` and e.g. `concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

    2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes a tuple of parameters.

  concatenate(_:to:).apply(to: tuple)
  tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

    3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put to the right of the operator to splat it in immediately, or it can be omitted to select a splattable version of the function.

  concatenate(_:to: *tuple)
  tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or not.

Any thoughts on these, or alternative approaches (as opposed to small syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #2

FWIW, I’m +1 on eliminating magic in the compiler and moving it to the stdlib (postfix ! is one example of that, I don’t recall offhand what prevents it from moving). However, I’d be reticent to start using ! for safe operations like dereferencing a (guaranteed memory safe) pointer.

-Chris

···

On Feb 10, 2016, at 1:21 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".


(Dave Abrahams) #3

So, since SE-0029 has been accepted, let's think about explicit
replacements. So far, I've been able to think of three general
approaches. For the examples below, I'll assume there's a `func
concatenate(number: Int, to string: String) -> String`, which does the
obvious thing. Where supported, I will fully qualify names with
SE-0021 syntax, but in some cases this might not be necessary.

Small request: please accompany proposal numbers with a title or link.
Thanks!

···

on Wed Feb 10 2016, Brent Royal-Gordon <swift-evolution@swift.org> wrote:

    1. Special parameter label.

`concatenate` is implicitly overloaded with a `func
concatenate(parameters: (Int, String)) -> String`.

  concatenate(parameters: tuple)
  tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument
label.
- Not clear how it would distinguish between `concatenate(_:to:)` and
e.g. `concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding
overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

    2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes
a tuple of parameters.

  concatenate(_:to:).apply(to: tuple)
  tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

    3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put
to the right of the operator to splat it in immediately, or it can be
omitted to select a splattable version of the function.

  concatenate(_:to: *tuple)
  tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or
not.

Any thoughts on these, or alternative approaches (as opposed to small
syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory
dereferencing, I would instead make them use postfix `!`. `!` could
become an `unwrapped` pseudo-property that any type can use,
democratizing another piece of `Optional` magic and working around the
vexing problem of what you name the `Pointer` property for "that thing
you're actually pointing to".

--
-Dave


(Jacob Bandes-Storch) #4

Weird idea based on #1: since some folks were discussing having "$$" as a
special identifier meaning "all arguments" in a closure, perhaps the
special parameter label could be $$ as well?

    concatenate(mytuple.0, mytuple.1)
    concatenate($$: mytuple)

···

On Wed, Feb 10, 2016 at 1:21 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

So, since SE-0029 has been accepted, let's think about explicit
replacements. So far, I've been able to think of three general approaches.
For the examples below, I'll assume there's a `func concatenate(number:
Int, to string: String) -> String`, which does the obvious thing. Where
supported, I will fully qualify names with SE-0021 syntax, but in some
cases this might not be necessary.

                1. Special parameter label.

`concatenate` is implicitly overloaded with a `func
concatenate(parameters: (Int, String)) -> String`.

        concatenate(parameters: tuple)
        tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument label.
- Not clear how it would distinguish between `concatenate(_:to:)` and e.g.
`concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

                2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes a
tuple of parameters.

        concatenate(_:to:).apply(to: tuple)
        tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

                3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put to
the right of the operator to splat it in immediately, or it can be omitted
to select a splattable version of the function.

        concatenate(_:to: *tuple)
        tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or not.

Any thoughts on these, or alternative approaches (as opposed to small
syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory
dereferencing, I would instead make them use postfix `!`. `!` could become
an `unwrapped` pseudo-property that any type can use, democratizing another
piece of `Optional` magic and working around the vexing problem of what you
name the `Pointer` property for "that thing you're actually pointing to".

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jonathan Tang) #5

I'm curious what the type signature of the splat operator would be? IIUC,
one of the primary reasons the Swift core team accepted SE-0029 (remove
implicit tuple splat) is because it adds a lot of complexity to the
typechecker. That was my experience too, having implemented a hobby
language several years ago where every function took a single tuple
argument. It made it very difficult to reason about the types in the
program, because a function type held no intrinsic type information, and
the only way to typecheck it was to know how the function was defined and
see whether that was compatible with how it was invoked. That, in turn,
made first-class functions and separate module compilation very difficult:
you had to carry around full information about the arity of the tuple, each
of its types, whether the types were generic type variables, whether they
had typeclass (protocol in Swift) constraints, whether the typeclasses
themselves had associated types, etc. And then when you try to *infer*
types, each of these become cases in the inference engine, and interacted
combinatorially.

I'd start by trying to write down the type of the splat operator. Can it
be expressed within the normal Swift type system? I'm not sure...I'm not
familiar enough with the intricacies of Swift tuple types to know.

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.

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). 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.

Note that there's a loss of expressiveness through *not* making the splat
operator first-class; a number of interesting Haskell combinators come out
of using $ (the function-application operator) as a function that can
itself be passed around. But it's also a question of how much complexity
you want to add to the language for the sake of conceptual purity. Python,
Ruby, and Javascript all seem to get along fine with just the ability to
splat lists & dictionaries into the final position of an argument list.

···

On Wed, Feb 10, 2016 at 1:21 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

So, since SE-0029 has been accepted, let's think about explicit
replacements. So far, I've been able to think of three general approaches.
For the examples below, I'll assume there's a `func concatenate(number:
Int, to string: String) -> String`, which does the obvious thing. Where
supported, I will fully qualify names with SE-0021 syntax, but in some
cases this might not be necessary.

                1. Special parameter label.

`concatenate` is implicitly overloaded with a `func
concatenate(parameters: (Int, String)) -> String`.

        concatenate(parameters: tuple)
        tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument label.
- Not clear how it would distinguish between `concatenate(_:to:)` and e.g.
`concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

                2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes a
tuple of parameters.

        concatenate(_:to:).apply(to: tuple)
        tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

                3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put to
the right of the operator to splat it in immediately, or it can be omitted
to select a splattable version of the function.

        concatenate(_:to: *tuple)
        tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or not.

Any thoughts on these, or alternative approaches (as opposed to small
syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory
dereferencing, I would instead make them use postfix `!`. `!` could become
an `unwrapped` pseudo-property that any type can use, democratizing another
piece of `Optional` magic and working around the vexing problem of what you
name the `Pointer` property for "that thing you're actually pointing to".


(Radek Pietruszewski) #6

There was a discussion about using `…` as an _array splat_ operator, i.e:

    func foo(xs: Int…)
    let xs = [1, 2, 3]
    foo(xs…)

That would be symmetric with “…” defining a variable length argument, and also the same approach as Ruby, but instead of `*xs` which looks like memory dereferencing, `xs…`, which IMHO works better as a symbol for this.

Perhaps it would be possible to overload this operator to perform _tuple splat_ as well. (I haven’t put it through tough thorough thought, though.)

— Radek

···

On 10 Feb 2016, at 22:21, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

So, since SE-0029 has been accepted, let's think about explicit replacements. So far, I've been able to think of three general approaches. For the examples below, I'll assume there's a `func concatenate(number: Int, to string: String) -> String`, which does the obvious thing. Where supported, I will fully qualify names with SE-0021 syntax, but in some cases this might not be necessary.

    1. Special parameter label.

`concatenate` is implicitly overloaded with a `func concatenate(parameters: (Int, String)) -> String`.

  concatenate(parameters: tuple)
  tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument label.
- Not clear how it would distinguish between `concatenate(_:to:)` and e.g. `concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

    2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes a tuple of parameters.

  concatenate(_:to:).apply(to: tuple)
  tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

    3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put to the right of the operator to splat it in immediately, or it can be omitted to select a splattable version of the function.

  concatenate(_:to: *tuple)
  tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or not.

Any thoughts on these, or alternative approaches (as opposed to small syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Craig Cruden) #7

Do you need access to the same functions using tuple-splat and non-tuple splat way — or just need a way to generically pass parameters from a tuple (homogeneous list of typed values - label accessible as content type) of variable length?

···

On 2016-02-11, at 4:21:32, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

So, since SE-0029 has been accepted, let's think about explicit replacements. So far, I've been able to think of three general approaches. For the examples below, I'll assume there's a `func concatenate(number: Int, to string: String) -> String`, which does the obvious thing. Where supported, I will fully qualify names with SE-0021 syntax, but in some cases this might not be necessary.

    1. Special parameter label.

`concatenate` is implicitly overloaded with a `func concatenate(parameters: (Int, String)) -> String`.

  concatenate(parameters: tuple)
  tuples.map(concatenate(parameters:))

Advantages:
- Does not require any new call-side syntax.
- Googleable thanks to use of identifiers.

Disadvantages:
- Could conflict with functions that use `parameters` as an argument label.
- Not clear how it would distinguish between `concatenate(_:to:)` and e.g. `concatenate(_:with:)`.
- Might reintroduce type checking complexity, since it's adding overloads.
- A bit wordy.
- As far as I know, not precedented in other languages.

    2. Method on functions.

`concatenate` has a method on it called, say, `apply(to:)` which takes a tuple of parameters.

  concatenate(_:to:).apply(to: tuple)
  tuples.map(concatenate(_:to:).apply(to:))

Advantages:
- You can be sure of the variant you're selecting.
- Googleable thanks to use of identifiers.
- Similar to usage in Javascript.

Disadvantages:
- Rather wordy, with lots of chaining and extra parentheses.
- Methods on unapplied functions might be a little confusing.

    3. Splat operator.

An operator like `*` is used to indicate splatting. A tuple can be put to the right of the operator to splat it in immediately, or it can be omitted to select a splattable version of the function.

  concatenate(_:to: *tuple)
  tuples.map(concatenate(_:to: *))

Advantages:
- You can be sure of the variant you're selecting.
- Similar to usage in Ruby and Perl 6.
- Fairly short in all forms.

Disadvantages:
- Not Googleable.
- New magic syntax.
- Two slightly different forms depending on whether you're calling or not.

Any thoughts on these, or alternative approaches (as opposed to small syntax tweaks)?

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #8

'!' produces an lvalue if its operand is an lvalue, which is something user-defined operators are currently unable to do.

-Joe

···

On Feb 10, 2016, at 1:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 10, 2016, at 1:21 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".

FWIW, I’m +1 on eliminating magic in the compiler and moving it to the stdlib (postfix ! is one example of that, I don’t recall offhand what prevents it from moving). However, I’d be reticent to start using ! for safe operations like dereferencing a (guaranteed memory safe) pointer.


(Brent Royal-Gordon) #9

However, I’d be reticent to start using ! for safe operations like dereferencing a (guaranteed memory safe) pointer.

A pointer dereference can cause a segfault. Even if you assume it's not pointing to unallocated memory, UnsafePointers can be nil, at least in Swift 2.

(The lvalue thing Joe Groff mentioned is also a concern, but adding inout return values would fix that in principle.)

···

--
Brent Royal-Gordon
Architechies


(Brent Royal-Gordon) #10

There was a discussion about using `…` as an _array splat_ operator, i.e:

   func foo(xs: Int…)
   let xs = [1, 2, 3]
   foo(xs…)

That would be symmetric with “…” defining a variable length argument, and also the same approach as Ruby, but instead of `*xs` which looks like memory dereferencing, `xs…`, which IMHO works better as a symbol for this.

Perhaps it would be possible to overload this operator to perform _tuple splat_ as well. (I haven’t put it through tough thorough thought, though.)

When I was looking up how different languages handle splatting, I noticed that `...` is PHP's splat operator. But keep in mind that, even though some of them can be typed pretty tightly if you want, Ruby, Perl 6, and PHP all have much looser *attitudes* towards typing than Swift; their splat operators are used to unpack heterogenous arrays into argument lists, with overflowing elements either being dropped or going into a specially-marked array parameter.

Swift is a very different language; this feature is meant for strongly-typed tuples, and is basically orthogonal to unpacking an array into a variadic parameter. I think that giving the "unpack tuple into multiple parameters" and "unpack array into a single variadic parameter" operators the same name would be confusing, and given that variadic parameters are declared with `...`, that operator should be reserved for variadic unpacking.

(By the way, are we going to drop variadic tuples? With the demise of parameters-as-tuples, I'm not sure there's much of a point in them anymore.)

···

--
Brent Royal-Gordon
Architechies


(Brent Royal-Gordon) #11

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.

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.

···

--
Brent Royal-Gordon
Architechies


(Chris Lattner) #12

There was a discussion about using `…` as an _array splat_ operator, i.e:

  func foo(xs: Int…)
  let xs = [1, 2, 3]
  foo(xs…)

That would be symmetric with “…” defining a variable length argument, and also the same approach as Ruby, but instead of `*xs` which looks like memory dereferencing, `xs…`, which IMHO works better as a symbol for this.

Perhaps it would be possible to overload this operator to perform _tuple splat_ as well. (I haven’t put it through tough thorough thought, though.)

When I was looking up how different languages handle splatting, I noticed that `...` is PHP's splat operator. But keep in mind that, even though some of them can be typed pretty tightly if you want, Ruby, Perl 6, and PHP all have much looser *attitudes* towards typing than Swift; their splat operators are used to unpack heterogenous arrays into argument lists, with overflowing elements either being dropped or going into a specially-marked array parameter.

Swift is a very different language; this feature is meant for strongly-typed tuples, and is basically orthogonal to unpacking an array into a variadic parameter. I think that giving the "unpack tuple into multiple parameters" and "unpack array into a single variadic parameter" operators the same name would be confusing, and given that variadic parameters are declared with `...`, that operator should be reserved for variadic unpacking.

Using … for this makes sense. Forwarding a varargs array using "foo(varargarray…)” would be the seemingly most obvious way to unpack “varargsarray” as the varargs list. Generalizing that to “foo(tuple…)” seems analogous and unambiguous.

(By the way, are we going to drop variadic tuples? With the demise of parameters-as-tuples, I'm not sure there's much of a point in them anymore.)

I’m pretty sure that variadic tuples are already dead, is there some case still limping along?

-Chris

···

On Feb 10, 2016, at 2:50 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Rainer Brockerhoff) #13

Would this, perhaps, solve the varargs forwarding problem?

public func Foo(items: Any...) {
  Bar($$)
}

···

On 2/10/16 19:52, Jacob Bandes-Storch via swift-evolution wrote:

Weird idea based on #1: since some folks were discussing having "$$" as
a special identifier meaning "all arguments" in a closure, perhaps the
special parameter label could be $$ as well?

    concatenate(mytuple.0, mytuple.1)
    concatenate($$: mytuple)

--
Rainer Brockerhoff <rainer@brockerhoff.net>
Belo Horizonte, Brazil
"In the affairs of others even fools are wise
In their own business even sages err."
http://brockerhoff.net/blog/


(Chris Lattner) #14

Right, makes sense, thanks.

-Chris

···

On Feb 10, 2016, at 2:12 PM, Joe Groff <jgroff@apple.com> wrote:

On Feb 10, 2016, at 1:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 10, 2016, at 1:21 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

P.S. As for pointers potentially using prefix `*` for memory dereferencing, I would instead make them use postfix `!`. `!` could become an `unwrapped` pseudo-property that any type can use, democratizing another piece of `Optional` magic and working around the vexing problem of what you name the `Pointer` property for "that thing you're actually pointing to".

FWIW, I’m +1 on eliminating magic in the compiler and moving it to the stdlib (postfix ! is one example of that, I don’t recall offhand what prevents it from moving). However, I’d be reticent to start using ! for safe operations like dereferencing a (guaranteed memory safe) pointer.

'!' produces an lvalue if its operand is an lvalue, which is something user-defined operators are currently unable to do.


(Jordan Rose) #15

Aside: I'm still hoping to make this Optionality explicit at some point, so that 'UnsafePointer' is known non-null and 'Optional<UnsafePointer>' may be null. But even then you can't be sure the memory hasn't been freed since you first got the pointer.

Jordan

···

On Feb 10, 2016, at 14:32, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

However, I’d be reticent to start using ! for safe operations like dereferencing a (guaranteed memory safe) pointer.

A pointer dereference can cause a segfault. Even if you assume it's not pointing to unallocated memory, UnsafePointers can be nil, at least in Swift 2.


(Jonathan Tang) #16

> 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:


(Andrew Bennett) #17

I like the first option, concatenate(parameters:)

It may not have issues conflicting if "parameters" was renamed to something
which is an invalid label, this would also make it clearer that it's doing
something special. For example:
    concatenate(#parameters: tuple)

It may be that it can also distinguish different labels if the label is
stored as part of the type information. If I remember correctly there is a
bug which would allow this when fixed.

For example:
    concatenate(#parameters: (123, with: "string"))

I'm fairly sure this should be sufficient, even in generics, the tuple just
needs to have the labels.

It may also be nice to allow unlabelled tuples if the function is
unambiguous.

The thread on tuples combining operators was probably this one:
Tuple conversion and type composition
http://comments.gmane.org/gmane.comp.lang.swift.evolution/5334

···

On Thursday, 11 February 2016, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>> wrote:

> 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.

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.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #18

I'm fairly sure this should be sufficient, even in generics, the tuple just needs to have the labels.

I've noticed a trend in comments from the core team towards thinking of argument labels as part of the function's name, not its type, so I was assuming that these tuple types would *not* have element labels.

···

--
Brent Royal-Gordon
Architechies


(Brent Royal-Gordon) #19

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:

Probably; otherwise this doesn't actually save the type checker much.

There's a bit of a complication when talking about type signatures. Currently, the fact that a function operates on a tuple is embedded in our type syntax, and the parentheses around the argument list are optional and represent a tuple—`(Int, String) -> String` is equivalent to `TupleOfIntAndString -> String`. This might now need a change, or at least a clarification. For the purposes of this post, I'll say that `(Int, String) -> String` is the type of a function taking an Int and String, while `((Int, String)) -> String` is the type of a function taking a tuple.

For the `concatenate(_:to:)` example, the original function has a signature of `(Int, String) -> String` (once argument labels are fully part of the name, which seems to be the trend). For each of the syntaxes I proposed, the type of this expression is `((Int, String)) -> String`:

  concatenate(parameters:)
  concatenate(_:to:).apply(to:)
  concatenate(_:to: *)

let f = concatenate(_:to:).apply(to:) // I assume this has type (Int, String) -> String
var g = f.apply(to:) // Is this legal? What's the type?

That's an interesting question. Swift does not have 1-tuples, so unary functions might not have an `apply(to:)` method.

// 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)) }
}

Without something along the lines of the `@splatting` annotation in a parameter list, it would not be possible to write a `compose(_:_:)` function which could operate on non-unary functions directly. Instead of writing `compose(f, g)`, you'd have to write `compose(f.apply, g.apply)`.

If you think that's kind of ugly, I don't disagree.

// 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)
}

Even with the current implicit splatting, this is impossible to express unless all functions have the same argument and return type. The generics system is simply not equipped to handle arbitrarily many generic types.

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'm not sure why you think it can't be stored. I think it can.

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.

I don't really think # is a good fit here. I think it's best used in places where it's performing a relatively straightforward textual substitution; in the `concatenate(_: #splat)` case, that's not really what's being done here.

(To tell the truth, in my head I imagine that when we finally get a macro system in Swift N, all macros are marked by a leading #. So I tend to get a little squeamish about # uses which draw heavily from context instead of relying on what you "pass" to them.)

···

--
Brent Royal-Gordon
Architechies


(Jonathan Tang) #20

> 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:

Probably; otherwise this doesn't actually save the type checker much.

There's a bit of a complication when talking about type signatures.
Currently, the fact that a function operates on a tuple is embedded in our
type syntax, and the parentheses around the argument list are optional and
represent a tuple—`(Int, String) -> String` is equivalent to
`TupleOfIntAndString -> String`. This might now need a change, or at least
a clarification. For the purposes of this post, I'll say that `(Int,
String) -> String` is the type of a function taking an Int and String,
while `((Int, String)) -> String` is the type of a function taking a tuple.

For the `concatenate(_:to:)` example, the original function has a
signature of `(Int, String) -> String` (once argument labels are fully part
of the name, which seems to be the trend). For each of the syntaxes I
proposed, the type of this expression is `((Int, String)) -> String`:

        concatenate(parameters:)
        concatenate(_:to:).apply(to:)
        concatenate(_:to: *)

> let f = concatenate(_:to:).apply(to:) // I assume this has type (Int,
String) -> String
> var g = f.apply(to:) // Is this legal? What's the type?

That's an interesting question. Swift does not have 1-tuples, so unary
functions might not have an `apply(to:)` method.

> // 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)) }
> }

Without something along the lines of the `@splatting` annotation in a
parameter list, it would not be possible to write a `compose(_:_:)`
function which could operate on non-unary functions directly. Instead of
writing `compose(f, g)`, you'd have to write `compose(f.apply, g.apply)`.

If you think that's kind of ugly, I don't disagree.

> // 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)
> }

Even with the current implicit splatting, this is impossible to express
unless all functions have the same argument and return type. The generics
system is simply not equipped to handle arbitrarily many generic types.

> 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'm not sure why you think it can't be stored. I think it can.

Not the result of the splatting operation, but the splatting operation
itself:

let legal = [f, g)
let illegal = [f.apply, g.apply] // But it certainly looks like an
expression that would normally be legal!

> 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.

I don't really think # is a good fit here. I think it's best used in
places where it's performing a relatively straightforward textual
substitution; in the `concatenate(_: #splat)` case, that's not really
what's being done here.

(To tell the truth, in my head I imagine that when we finally get a macro
system in Swift N, all macros are marked by a leading #. So I tend to get a
little squeamish about # uses which draw heavily from context instead of
relying on what you "pass" to them.)

The "..." syntax that's been batted around here looks fine to me as well.

···

On Wed, Feb 10, 2016 at 8:37 PM, Brent Royal-Gordon <brent@architechies.com> wrote: