What would it take to automate empty parentheses/brackets removal, and depluralize?

My guess is "an attribute", and that it won't ever happen. But just in case I am wrong, I'll document what I'm talking about.

Given this type used in all the good apps…

struct Cat {
  enum Snack: CaseIterable {
    case 🐁, 🦇, 🦐, 🐹, 🕊
  }


  let name: String
  let favoriteSnack: Snack
}

…sometimes you want to denote that empty parentheses should be disallowed, and actually turn into nothing…

extension Cat {
  enum ParenthesesExample {
    static var cat: Cat { cat() }

    static func cat(
      name: String = "Cathulhu",
      favoriteSnack: Snack = Snack.allCases.randomElement()!
    ) -> Cat {
      .init(
        name: name,
        favoriteSnack: favoriteSnack
      )
    }
  }
}

Cat.ParenthesesExample.cat(name: "Catman", favoriteSnack: .🦇)
Cat.ParenthesesExample.cat(name: "Catman")
Cat.ParenthesesExample.cat(favoriteSnack: .🦇)
Cat.ParenthesesExample.cat()
Cat.ParenthesesExample.cat

…and other times, you've got a similar situation, but instead, it's brackets, and a depluralization:

extension Cat {
  enum BracketsAndDepluralizationExample {
    static var cat: Cat { cats[] }

    enum cats {
      static subscript(
        name name: String = "Cathulhu",
        favoriteSnack favoriteSnack: Snack = Snack.allCases.randomElement()!
      ) -> Cat {
        .init(
          name: name,
          favoriteSnack: favoriteSnack
        )
      }
    }
  }
}

Cat.BracketsAndDepluralizationExample.cats[name: "Catman", favoriteSnack: .🦇]
Cat.BracketsAndDepluralizationExample.cats[name: "Catman"]
Cat.BracketsAndDepluralizationExample.cats[favoriteSnack: .🦇]
Cat.BracketsAndDepluralizationExample.cats[]
Cat.BracketsAndDepluralizationExample.cat

In other words, both functions and subscripts fill the role of supplying a syntax for "adding parameters to properties", but there's no language-level conversion of the concept of switching between the two.

Like this?

struct Cat {
  enum Snack: CaseIterable {
    case 🐁, 🦇, 🦐, 🐹, 🕊
  }

  let favoriteSnack: Snack
}

extension Cat {
  enum ParenthesesExample {
    static var cat: Cat { cat(favoriteSnack: Snack.allCases.randomElement()!) }

    static func cat(favoriteSnack: Snack) -> Cat {
      .init(favoriteSnack: favoriteSnack)
    }
  }
}

Cat.ParenthesesExample.cat(favoriteSnack: .🦇)
//Cat.ParenthesesExample.cat() // Cannot call value of non-function type 'Cat'
Cat.ParenthesesExample.cat

extension Cat {
  enum BracketsAndDepluralizationExample {
    static var cat: Cat { cats[favoriteSnack: Snack.allCases.randomElement()!] }

    enum cats {
      static subscript(favoriteSnack favoriteSnack: Snack) -> Cat {
        .init(favoriteSnack: favoriteSnack)
      }
    }
  }
}

Cat.BracketsAndDepluralizationExample.cats[favoriteSnack: .🐹]
//Cat.BracketsAndDepluralizationExample.cats[] // Missing argument for parameter 'favoriteSnack' in call
Cat.BracketsAndDepluralizationExample.cat

No, that's just writing both types of definitions. (Consider other examples with more and more arguments, all with defaults. Edit: I added one more argument; the number of overloads required is 2^argumentCount.)

Methods with names that begin with verbs, that are not the thing that they return, are handled well by default arguments. The empty parentheses denote the action being invoked.
Methods that are named the thing that they return are a different concept (properties with parameters), but use the same syntax. “Named subscripts” are sometimes the right solution, when you’re “grabbing from a collection”, but they require hackery like shown above, as they’re not really supported by the language.

I think it’s worth pointing out that the Swift API design guidelines specify that static factory methods should be prefixed with make- so that the no-argument case would read as makeCat().

This isn’t a direct solution to your problem, of course, but it addresses the verb-noun issue you raised.

Also, apologies for the double-post, but FWIW this can be accomplished with 'only' N overloads for a factory with N arguments:

func foo(arg1: Int) {}
func foo(arg1: Int = 0, arg2: Int) {}
func foo(arg1: Int = 0, arg2: Int = 0, arg3: Int) {}

foo() // Error!
foo(arg1: 0)
foo(arg2: 0)
foo(arg3: 0)
foo(arg1: 0, arg2: 0)
foo(arg1: 0, arg3: 0)
foo(arg2: 0, arg3: 0)
foo(arg1: 0, arg2: 0, arg3: 0)
1 Like

Good braining! :brain:

I definitely use the "make" prefix, but…

  1. The guidelines don't define what a factory method is, leaving it up to define it ourselves. (I think it's a naming convention for the missing "nested extension initializer" feature.)
  2. I just wanted to post something that would compile. My question is indeed about "accessors" (my definition for those being "properties that take zero or more arguments"). Frequently, those won't make new instances, but there's nothing in the language to stop you from doing that, and often, a new instance is a backup plan in case something else doesn't already exist.