[proposal] Allow function argument type to be omitted when passing a default value from which it can be inferred


(Sam Dods) #1

I propose that function argument types could be omitted in the same way as variable and property argument types are omitted when they are set at point of definition.

At present the type of properties and variables can be inferred as below:

class EvolutionManager {
  let proposalManager = ProposalManager() // type is inferred

  func proposeTopic(topic: String) {
    let evolution = Evolution(topic: topic) // type is inferred
    proposalManager.proposeEvolution(evolution)
  }
}

However, function arguments cannot be inferred, as below:

class EvolutionManager {
  func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager()) { // compiler error, cannot infer type
    proposalManager.proposeEvolution(evolution)
  }
}

It's a nice feature of the language that the type can be inferred for properties and variables, so I don't see any reason not to allow the same for function arguments. It allows for more concise code, and adds consistency with other language features. And I don't personally think it would make our code any harder to read.

There are of course some cases where the type cannot be inferred, i.e. when the type should actually be a protocol type and the default value is a concrete type. Consider the following:

protocol ProposalHandler {
  associatedType P : Proposable
  propose(p: P)
}

class EvolutionManager {
  // the type would be inferred as the concrete type `ProposalManager` and could not be
  // called with any other argument type that conforms to the `ProposalHandler` protocol,
  // as may have been the intention
  func proposeEvolution(evolution: Evolution, proposalHandler = ProposalManager())

  // instead it should be written as follows:
  func proposeEvolution(evolution: Evolution, proposalHandler: ProposalHandler = ProposalManager())
}

But this is the same for properties and variables, so it should not be a reason to not allow inferring of function arguments. For example:

class EvolutionManager {
  // the property is inferred as the concrete type `ProposalManager` and may not
  // be set to any other value of type that conforms to the `ProposalHandler` protocol,
  // as may have been the intention.
  var proposalHandler = ProposalManager()

  // instead it should be written as follows:
  var proposalHandler: ProposalHandler = ProposalManager()
}

What do people think of this?

It would have no impact on existing code, and it's not the kind of thing that needs an alternative solution.


(Dominik Pich) #2

I think it would add consistence… everywhere else types can be inferred… why not here

···

On May 10, 2016, at 12:02 PM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:

I propose that function argument types could be omitted in the same way as variable and property argument types are omitted when they are set at point of definition.

At present the type of properties and variables can be inferred as below:

class EvolutionManager {
let proposalManager = ProposalManager() // type is inferred

func proposeTopic(topic: String) {
   let evolution = Evolution(topic: topic) // type is inferred
   proposalManager.proposeEvolution(evolution)
}
}

However, function arguments cannot be inferred, as below:

class EvolutionManager {
func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager()) { // compiler error, cannot infer type
   proposalManager.proposeEvolution(evolution)
}
}

It's a nice feature of the language that the type can be inferred for properties and variables, so I don't see any reason not to allow the same for function arguments. It allows for more concise code, and adds consistency with other language features. And I don't personally think it would make our code any harder to read.

There are of course some cases where the type cannot be inferred, i.e. when the type should actually be a protocol type and the default value is a concrete type. Consider the following:

protocol ProposalHandler {
associatedType P : Proposable
propose(p: P)
}

class EvolutionManager {
// the type would be inferred as the concrete type `ProposalManager` and could not be
// called with any other argument type that conforms to the `ProposalHandler` protocol,
// as may have been the intention
func proposeEvolution(evolution: Evolution, proposalHandler = ProposalManager())

// instead it should be written as follows:
func proposeEvolution(evolution: Evolution, proposalHandler: ProposalHandler = ProposalManager())
}

But this is the same for properties and variables, so it should not be a reason to not allow inferring of function arguments. For example:

class EvolutionManager {
// the property is inferred as the concrete type `ProposalManager` and may not
// be set to any other value of type that conforms to the `ProposalHandler` protocol,
// as may have been the intention.
var proposalHandler = ProposalManager()

// instead it should be written as follows:
var proposalHandler: ProposalHandler = ProposalManager()
}

What do people think of this?

It would have no impact on existing code, and it's not the kind of thing that needs an alternative solution.

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


(Chris Lattner) #3

We have a pretty strict rule here: types are allowed to be inferred in implementations, but not interfaces. This is important for efficient compilation, encourages people to think about their API interfaces, and somewhat reduces the damage when they don’t.

-Chris

···

On May 10, 2016, at 3:02 AM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:

I propose that function argument types could be omitted in the same way as variable and property argument types are omitted when they are set at point of definition.

At present the type of properties and variables can be inferred as below:

class EvolutionManager {
let proposalManager = ProposalManager() // type is inferred

func proposeTopic(topic: String) {
   let evolution = Evolution(topic: topic) // type is inferred
   proposalManager.proposeEvolution(evolution)
}
}

However, function arguments cannot be inferred, as below:

class EvolutionManager {
func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager()) { // compiler error, cannot infer type


(Douglas Gregor) #4

I propose that function argument types could be omitted in the same way as variable and property argument types are omitted when they are set at point of definition.

At present the type of properties and variables can be inferred as below:

class EvolutionManager {
let proposalManager = ProposalManager() // type is inferred

func proposeTopic(topic: String) {
  let evolution = Evolution(topic: topic) // type is inferred
  proposalManager.proposeEvolution(evolution)
}
}

However, function arguments cannot be inferred, as below:

class EvolutionManager {
func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager()) { // compiler error, cannot infer type

We have a pretty strict rule here: types are allowed to be inferred in implementations, but not interfaces. This is important for efficient compilation, encourages people to think about their API interfaces, and somewhat reduces the damage when they don’t.

We broke this rule when we started allowing properties at type and module scope to have inferred types. I was moderately opposed to it at the time, but we have it, we're going to keep it, and this suggestion for inferring parameter types from default arguments seems to fit in the same category.

That said, I have implementation concerns: we don't want the expression type checker to be involved when figuring out the type of a generic function, so we would need to define this in a way that doesn't affect the computation of generic signatures for a function. Probably, such types just aren't involved in inferring generic constraints.

  - Doug

···

Sent from my iPhone

On May 10, 2016, at 8:33 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:02 AM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:

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


(Sam Dods) #5

yes, exactly Doug... the properties at type level are part of the interface and their types can be inferred, so i don't see the difference with function argument types.

i just find it really bulky when writing something like this:

func doSomething(thing, withManager: SomethingManager = SomethingManager()) {
  // prepare the thing
}

I think this meets the API guidelines for "omitting needless words"

And it should be invalid syntax to expect the type to be inferred for a generic argument (i.e. no change from current behaviour)

···

On 11 May 2016, at 04:53, Douglas Gregor <dgregor@apple.com> wrote:

Sent from my iPhone

On May 10, 2016, at 8:33 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 3:02 AM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:
I propose that function argument types could be omitted in the same way as variable and property argument types are omitted when they are set at point of definition.

At present the type of properties and variables can be inferred as below:

class EvolutionManager {
let proposalManager = ProposalManager() // type is inferred

func proposeTopic(topic: String) {
let evolution = Evolution(topic: topic) // type is inferred
proposalManager.proposeEvolution(evolution)
}
}

However, function arguments cannot be inferred, as below:

class EvolutionManager {
func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager()) { // compiler error, cannot infer type

We have a pretty strict rule here: types are allowed to be inferred in implementations, but not interfaces. This is important for efficient compilation, encourages people to think about their API interfaces, and somewhat reduces the damage when they don’t.

We broke this rule when we started allowing properties at type and module scope to have inferred types. I was moderately opposed to it at the time, but we have it, we're going to keep it, and this suggestion for inferring parameter types from default arguments seems to fit in the same category.

That said, I have implementation concerns: we don't want the expression type checker to be involved when figuring out the type of a generic function, so we would need to define this in a way that doesn't affect the computation of generic signatures for a function. Probably, such types just aren't involved in inferring generic constraints.

- Doug

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


(Tino) #6

If seen several suggestions to extend inference now, and although it is convenient, it can make it harder to understand what's happening.
Imho it would be possible to combine the benefits of both ways without their disadvantages:
Xcode already has the ability to correct some errors and warnings, but always asks the user first ("hey, shall I replace this var with let?"), and this is good in general, because silently changing code could lead to errors (when I declare something as constant, it's most likely an error when I try to modify the value afterwards).

Coming back to this specific proposal:

func proposeEvolution(evolution: Evolution, proposalManager = ProposalManager())

is an easy case - but what about
func proposeEvolution(evolution: Evolution, proposalManager = someFunctionWhoseReturnTypeIsntObvious())

It would be possible to create a tool that complements the source by adding the type, but I don't think Swift-evolution is the right place to discuss this, as it's not actually about the language itself.
But: Swift has the benefit that not only the development of the language, but also the compiler, IDE and all essential tools are driven by a single company. Therefore it easier (at least I hope so) to evolve language and tools side by side, and it would be possible to add features that only work in combination.
One problem with inferred information is that its origin can't be recognized unless it is annotated, so that tools could see that (for example) the user didn't care wether x is a variable or constant, and could change the type whenever it makes sense to do so.
The only way to achieve this now would be a special comment like
/*?*/let x = 0
which, of course, is way to ugly to be useful.

I did not investigate how big the performance-penalty for compilation of code that makes heavy use of type inference is, but I guess "caching" could help here as well.
This could be done by adding special characters (I've no good idea for this, so I'll use the general-purpose special character ;-):
func proposeEvolution(evolution: Evolution, proposalManager: #ProposalManager = someFunctionWhoseReturnTypeIsntObvious())


(Pyry Jahkola) #7

You can get rid of the repetition here by using the implicit member expression syntax (i.e. dot-prefixing the initialiser):

    func doSomething(thing, withManager: SomethingManager = .init()) { ... }

— Pyry

···

On 07 Jun 2016, at 22:08, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:

yes, exactly Doug... the properties at type level are part of the interface and their types can be inferred, so i don't see the difference with function argument types.

i just find it really bulky when writing something like this:

func doSomething(thing, withManager: SomethingManager = SomethingManager()) {
  // prepare the thing
}