Unordered function parameters (and Python **kwargs)

When dealing with Python, you may see functions in which the last parameters can have any name (and type). This is achieved by using a dictionary and a custom splatting operator for dictionaries:

def foo(**kwargs):
    print(kwargs.get('text', 'title'))
    print(kwargs.get('color', 'black'))
    print(kwargs.get('size', 14))

fun(text='My Text')
fun(text='My Colored Text', color='red')
fun(text='My Big Text', size=72)

This concept isn't available in Swift and functions like the one above are usually implemented in the following strictly typed way:

func foo(text: String = "title", color: Color = .black, size: Int = 14) {
    print(text)
    print(color)
    print(size)
}

foo(text: "My Text")
foo(text: "My Colored Text", color = .red)
foo(text: "My Big Text", size: 72)

The benefit of **kwargs is that you can type parameters in the order you prefer (omitting some parameters ― if you want to ― that will necessarily fallback to default values), but the downside is that you can't have an associated type to a specific parameter (e.g. having text: String, size: Int).
Swift, on the other end, forces you to type parameters in the order they appear in the function declaration (it suggests a fix-it to quickly resolve ordering issues, so that's not a problem) and autosuggests the whole function declaration:

With Xcode 12 you can now see all the parameters at once in the suggestion description.
However, it displays only one of the possibile initializer variants and even if you start typing a parameter name (such as exclusiveTouch) it would fail autocompleting it:

How to solve custom ordering and autocompletion with Swift 5

If you really need custom ordering, autocompletion and strict typing you may decide to have an enum associated to your function:

enum FooParameter {
    case text(String)
    case color(Color)
    case size(Int)
}

func foo(_ kwargs: FooParameter...) {
    var text: String = "title"
    var color: Color = .black
    var size: Int = 14
    
    for arg in kwargs {
        switch arg {
        case .text(let x):
            text = x
        case .color(let x):
            color = x
        case .size(let x):
            size = x
        }
    }
    
    print(text)
    print(color)
    print(size)
}

foo(.color(.blue), .text("My Colored Small Text"), .size(8))

Xcode will only suggest the available parameter names (with repetition however) having their associated type in the description.

Proposed syntactic sugar

If the Swift community appreciates, I propose the following syntax to define typed **kwargs:

func foo([text: String = "title", color: Color = .black, size: Int = 14]) {
    print(text)
    print(color)
    print(size)
}

// all equivalent calls
foo(text: "My Colored Small Text", size: 8, color: .blue)
foo(text: "My Colored Small Text", color: .blue, size: 8)
foo(color: .blue, text: "My Colored Small Text", size: 8)

The following rules would apply:

  • kwargs need to be declared in square brackets;
  • kwargs can only be declared in functions (not closures);
  • kwargs must be declared at the end of the function parameters list;
  • kwargs must provide default values for every parameter;
  • kwargs can only be declared once per function declaration.

Optionally:

  • function overrides with the same "static" parameters (the ones outside the [] brackets) should throw an "Invalid redeclaration" compiler error;
  • kwargs may contain closures but they cannot be written as trailing closures on the call site.

Syntax reasoning

It's frequent in API documentation to denote optional arguments using square brackets, such as in Mozilla Developer Documentation:

arr.slice([start[, end]])

which means that the end parameter may be omitted, obtaining the following:

arr.slice([start])

and the same applies for the start parameter, obtaining the following:

arr.slice()

The meaning of the square brackets in the proposal is similar, they provide additional (unordered) parameters that can be omitted entirely.

Parameter definitions highly resemble the dictionary syntax.

How Xcode autosuggestions would work

Let us consider the following struct declaration:

struct VGrid<Content>: View {
    let content: Content
    let columns: [GridItem]
    let spacing: CGFloat
    let alignment: HorizontalAlignment
    let pinnedViews: PinnedScrollableViews?

    init(content: Content, columns: [GridItem], [
        spacing: CGFloat = 10,
        alignment: HorizontalAlignment = .center,
        pinnedViews: PinnedScrollableViews? = nil
    ]) { ... }
}

Typing "VGrid(" we should see:

VGrid(content:columns:)
VGrid(content:columns:alignment:...)
VGrid(content:columns:spacing:...)
VGrid(content:columns:pinnedViews:...)

Typing "VGrid(content: (...), columns: (...), s" we should get autocompletion for "spacing" and the following suggestions:

VGrid(content:columns:spacing:)
VGrid(content:columns:spacing:alignment:...)
VGrid(content:columns:spacing:pinnedViews:...)

Typing "VGrid(content: (...), columns: (...), spacing: (...), a" we should only see:

VGrid(content:columns:spacing:alignment:)
VGrid(content:columns:spacing:alignment:pinnedViews:)

Even though I'm suggesting how Xcode completion would work, I don't really know if its behavior is defined in the Language Server Protocol and so if the community can directly contribute to it.

The purpose of this thread is to discuss about the feature, to see how much or little consensus it can get from the community.
I don't personally think that it would be useful for the majority of the user base since function declarations with a long list of parameters are very unlikely to be found, but I may be wrong (especially for the custom ordering of parameters), so here's an informal poll in which you can vote for o against:

  • Positive opinion
  • Neutral opinion
  • Negative opinion

0 voters

@dynamicCallable would be very similar to python **kwargs if we added a choice to use Dictionary in addition to KeyValuePairs, but **kwargs are very different from what you're proposing.

Is there a reason there cannot be any required unordered parameters?

Is this about unordered arguments, or optional arguments? Swift already has optional arguments and they use totally different syntax

That would be a great way for autosuggestion to work, with or without unordered function parameters! What happens today is awful.

And the most important question: I don't see a situation where unordered arguments are better than ordered. Where would you use that? When arguments are just a bunch of options with no inherent order? In that situation I think that arbitrary, stable order is better than unordered.

1 Like

Before Swift 3, Swift used to have this feature for parameters with default values as part of a Python-like model for keyword arguments. It was removed in a previous proposal, SE-0060:

3 Likes

Thank you for joining the discussion. I see negative opinions are rising pretty fast but without explicit reasons. I still need to figure under which criteria something can be proposable or not for the Swift language and reasoned discussions will help me a lot in these first steps.

As I understand, @dynamicCallable requires a fixed and shared type for values and, since keys can be of any kind, the compiler cannot suggest anything to help you writing the function signature.
My proposal lets you specify a finite and fixed set of parameter names with their uniquely defined types and default values.

I get that Python **kwargs are different, but in my opinion typed parameters should be the goal over a generic list of KeyValue<String, Any>, which would pollute both the compiler and the function bodies with type castings, if you choose to follow the @dynamicCallable path.

"Optional" has a specific meaning in Swift and I wasn't referring to that. I was referring instead to parameters that may or not be present in the function call. To clarify:

// optional parameter type, you cannot omit `bar` in the function call
func foo(bar: Int?) {}

// optional parameter (what I meant), you can omit `bar` the function call
func foo(bar: Int? = nil) {}
func foo(bar: Int = 1000) {}

How should I call optional parameters? Even "discardable" is a used word in Swift.

Swift's forced ordered parameters has brought pretty expressive API designs (and I really like them, to be honest, since they express and clarify at a glance what the function or method should do even without looking in its documentation page). I'd personally like to have required parameters to be ordered, but I'm open to suggestions, of course.
From my point of view, whatever you put in those square bracket should not be required (analogously to the fact that Python **kwargs can have no key-value pairs ― or an empty dictionary to be more precise).

Pretty much only for the Xcode suggestions now that I'm thinking on that a bit more. If parameters have a fixed order, Xcode would be forced to only suggest either the whole function signature, or the combinations with partially removed parameters.
If you start typing the name of a parameter near the end of the function call, should Xcode still suggest a parameter that's been declared before in the function signature?

1 Like

Thanks! I passed the last two days searching on these forums for something similar to my feature. I haven't looked in the actual swift-evolution repository.

I see that Erica Sadun went with the same layout as mine:

While I do take advantage of this feature, it would be less than honest to point out that a large portion of such usage is to point out how cool the ability is.

That said, what I'm really doing is treating them in code like an ad hoc set of enumerated cases with associated values. Perhaps rethinking about them in that light would be better than simply removing them from the language?

Also, from Matthew Johnson:

One place where I believe argument re-ordering is useful is with memberwise arguments when you are initializing an instance. Order usually plays no significant role with these arguments which are in some sense similar to assignment statements (which are inherently re-orderable).

In fact, I have found myself wishing non-defaulted memberwise initializer parameters were re-orderable at times, especially when using the implicit memberwise initializer for a struct. Source order for property declarations does not always match what makes the most sense at the initialization site (something that was pointed out numerous times during the review of my memberwise init proposal).

The fact that a similar feature has been already explicitly deprecated doesn't leave much escape for alternatives.

Honestly, I wouldn't much care for the order of the parameter. I'd much prefer that the autocomplete is better annotated.

foo(text: <#String#>)

vs

foo(text: <#String (Defaulted)#>)

Because, well, I'll need to read through the list of default-able parameters to looks for the ones I need anyhow. The parameter order really doesn't matter (for the caller side).

2 Likes

Yep, what you are suggesting here is better than python's kwargs! I just wanted to point out that, because title is suggesting that it would be equivalent

"Nonrequired" maybe? I'll rephrase my question using this word:

Syntax reasoning talks about nonrequired arguments, but the rest of the post talks about unordered arguments. Swift already has a syntax for nonrequired arguments that is very different from the one in the mozilla documentation ¯\_(ツ)_/¯

Why not? :D It already happens in a way! If you write

func f(arg2: Int) { }
func f(arg1: Int, arg2: Int) { }
f(arg2

and trigger autocompletion, it would suggest both functions, and insert text both before and after what you wrote :)

Defaulted param?

1 Like

I would love to implement the autocompletion you're suggesting, but SwiftCompletion.cpp looks complicated, and I am unable to compile swift on either linux or macos :(

I now have a better understanding on why this feature isn't needed, mainly because of the reasons behind the proposal SE-0060 and @Lantua's point of view:

I was trying to replicate some Python API designs and that's the main reason why I came with this feature. In those function calls (or initializers), kwargs were used to place additional properties such as line width, line color, marker size, marker color, marker shape, etc. in a plot initializer. That's a recurrent pattern in MATLAB too: you have a long list of properties and options and you can immediately set them in the call, without editing the plot object after its initialization.


Regarding the unsorted parameters, Xcode already fixes ordering issues and I now better understand how the autocompletion is intended to work too:

  • you type the function name;
  • Xcode suggests every function signature, with the addition of the ones that remove all the defaulted parameters;
  • you choose one of the function signatures and press Enter to have the full signature inserted with type placeholders;
  • you manually replace arguments and remove defaulted ones if needed.

My workflow was more incremental and that's the reason why it doesn't fit with how Xcode works. Let's say we have

func foo(a: Int = 2, b: Int = 3, c: Int, d: Int) {}

Typing foo we'll get the two signatures: the one with all the parameters and the one with only the required parameters.

 1: foo                // suggests: foo(a:b:c:d:), foo(c:d:)
 2: foo(               // suggests: (a:b:c:d:), (c:d:)
 3: foo(a              // suggests: (a:b:c:d:)
 4: foo(a: 0,          // suggests: b:, c: only with ESC key
 5: foo(a: 0, c        // suggests: c:
 6: foo(a: 0, c: 1, d  // fails to suggest d:

On line 4, after I type the comma, I expect the completions to automatically show but that's not the case. You need to explicitly toggle them with the esc or ctrl+space keys, but that's not a relevant issue.

Why on line 6 Xcode fails to suggest the parameter d even though it comes after c in the function signature? If you instead of typing d, decide to type c a second time

 7: foo(a: 0, c: 1, c  // suggests: c:

Xcode will suggest c again even though you already wrote it. So the autocompletion is capable of suggesting single parameter names, thus making you believe you can incrementally type the function call, but instead it fails.

It follows that the intended way to use the autocompletion is to select the full function signature and to manually replace placeholders.

1 Like

Maybe we can do something like this?

struct PlotProperties { ... }
func foo(properties: PlotProperties) { ... }

foo(properties: .init(thickness: 30, ...))

That's why it'd be nice if I can see which argument is defaulted w/o relying on the documentation.

1 Like

I'm afraid that using a separate structure for properties could make things worse for the end user. But thank you for the suggestion, that's a pattern that in a way I used frequently in JavaScript, since there aren't named parameters. I feel I may stick with either a long initializer or forcing the user to set the properties after the plot initialization.


I feel like autocompletion issues should eventually be discussed/proposed in a separate and more focused thread to gain the right audience. Code completion is even explicitly stated in the Swift 6 road map: