Which (breaking) changes would you be willing to accept in a major language release?

I would argue that doesn't really matter, because it's the humans who can't differentiate it. I'd posit that most people weren't aware that NSObject is both a protocol and a class. It's confusing.

2 Likes

A thousand times yes!

3 Likes

I'd like to see Result and Optional rationalised. It keeps coming up and everyone and their dog has a different solution. Even me: Introducing an “Unwrap or Throw” operator - #26 by jjrscott

I do like @tera's idea, but I'm not sure how I feel about more dots:

1 Like

I think a warning should suffice. We don’t need to change all the semantics of protocol conformances. Of course, suppressing that error may be hard to do elegantly, but I think this is a more viable solution.

Again, I think requiring this marker may be too aggressive but I’m definitely in favor of adding such a feature. Maybe autocomplete could add this marker automatically, but even adding it to default implementations in protocol extensions would be incredibly useful.

1 Like

I'd love to see Optional removed in favor of Result with a MissingValueError (With all conveniences and syntax sugar intact.

Something that I just remembered:

  • I'd love the "everything can be an AnyObject" trap to be gone on Darwin platforms at least from the Swift perspective, I personally don't mind whatever Objective-C can do.
1 Like

So far I'm happy to see more community members to join and share their personal opinions and differences for the language they would like to see.
Some things I agree with, some other things I personally strongly disagree. However this isn't the thread to publicly disagree with other opinions, not even if some of them do sound very extreme. It is the exact opposite, everyone should accept ANY opinion (as long as those are not intentionally meant as a joke).

Keep going! :+1:

3 Likes

There are bunch of great and intriguing ideas in the thread so far! Here's some from my wishlist...

  • Closed protocols (ie, protocols that are public but cannot be adopted by anyone). There are times when I want to express that some types are all related, but I intentionally do no support anyone else making a related type. CKRecordValueProtocol is one such protocol that would benefit from this, IMO.
  • public import, internal import, and private import.
  • Overhaul access control stuff so I can finally do things like typeprivate, public-and-open-but-only-for-subtypes, friend, etc
  • Death to fileprivate
  • also: module CustomModule { ... }
  • add the !! and ?! operators (ie "crash if nil" and "throw if nil", respectively). These are ridiculously helpful when doing custom decoding logic:
    init(from anyJSON: Any) {
        // before:
        guard let json = anyJSON as? [String: Any] else { 
            fatalError("Getting anything except a dictionary is a logic bug") 
            // yes, I can use `as!` here, but if I have a way to give a custom error message,
            // I'd like to provide one
        }
        guard let value = json["key"] as? String else { throw SomeJSONError... }
        self.value = value
    
        // after:
        let json = anyJSON as? [String: Any] !! "Getting anything except a dictionary is a logic bug"
        self.value = try (json["key"] as? String) ?! SomeJSONError...
    }
    
  • complete interoperation between throws methods and result methods. Example:
    // given
    func myThrowingFunction() throws -> Foo { ... }
    // this should be callable as either:
    let result = myThrowingFunction() // "result" is a Result<Foo, Error>
    // or (currently)
    let foo = try myThrowingFunction() // "foo" is a Foo
    
    ... perhaps for async methods as well, although I haven't thought about that as much.
  • typed throws
  • a lot more synthesis around enums (isCase: Bool and var casePayload: PayloadType? accessors)
  • deprecate and replace Codable
3 Likes

:heart:

6 Likes

+1000. the current Codable design is fine for infrequently-called functionality, but the performance is just terrible for high-throughput use cases.

5 Likes

There are a lot of ideas in this thread that I both agree and disagree with, but this has been one of my #1 feature requests for a long time.

I wish try was applied after performing functions on Result so method chaining could express behavior like:

let value = try doFoo()
  .logError(logger) { "Failed to do bar: \($0)" }
let value = try doFoo()
  .replaceError(fallbackValue)

In a similar vein, I wish try was expressible in a library so similar control flow keywords could be rewritten for Optional and Bool. ex:

unwrap returnsOptional() // returns nil from the calling function if returnsOptional returns nil 
require returnsBool() // returns false from the calling function if returnsBool returns false

This could enable code like:

require throwingFn()
  .logError ...
  .isSuccess
1 Like

:100: from me.

It's a bit less succinct as in your example but we can do this already:


let fromThrowingToResult = Result { try foo() }
let fromResultToThrowing = try! bar().get()

func foo() throws -> Int {
    if (x & 1) == 0 {
        throw NSError(domain: "", code: -1, userInfo: nil)
    } else {
        return x + 1
    }
}

func bar() -> Result<Double, Error> {
    if y < 0 {
        return .failure(NSError(domain: "", code: -1, userInfo: nil))
    } else {
        return .success(sqrt(y))
    }
}

Do you think it can be optimised in the current form without complete rehaul / deprecation / replacing, or is there some inherent drawback deep in its design that makes it slow?

Note, that if you write it on a single line it becomes:

try doFoo().logError()
try doFoo().replaceError()

which shows the need for parens:

(try doFoo()).replaceError()

With trailing syntax for throw chaining could have been more natural:

doFoo().try.replaceError()

and I'd put try after each throwing function (similar to how we can put more than one ! or ?):

_ = foo?.bar?.baz
_ = quz.try.quux.try

ditto for await.

var allIWantForSwiftmas: Is? = nil
10 Likes
  • changing .filter to .filtered (could be backwards-compatible)
  • implicit returns
  • if expressions (could be backwards-compatible)
  • more expression-based syntax instead of statement-based syntax (could be backwards-compatible)

Quite a breaking change: denote the minimal integer value (for signed integers, so -128 for Int8, -32768 for Int16, etc), and the maximum integer value (for unsigned integer values) as an overflow marker, that would be propagated across integer ops and end up in the result. As an alternative for signs integers use one-complement representation and denote -0 value for overflow marker. The range of integers will become one value smaller which might be a show stopper for some algorithms but ok for (hopefully) most. overflow == overflow and we can make an arbitrary ruling that overflow > any non overflown int

If that's done we can eventually deprecate trapping on int math overflows, or make it optional so developer can choose between the two behaviours (actually three as there's &+ and co).

I wish we could have fitted in the renaming of dropFirst / dropLast, before the source stability bar was raised. The tense of the verb is wrong, and drop sounds so much more destructive than (for example) removingFirst().

Whilst we maybe technically could still do this change, with a long tail of deprecation, the third-party ecosystem of libraries have also now matched the stdlib names (like in Combine). So it feels somewhat stuck in stone as a quirk of history at this point.

4 Likes

the protocol existential dispatch gets the most blame, but it also has to do with the way UnkeyedDecodingContainer and friends track their codingPath. every decoding operation pushes a debugging breadcrumb onto the stack, which is wasteful and only needed if decoding actually fails.

this is why swift-json opted for a functional (instead of protocol-oriented) decoding interface, which only logs debugging traces if an error is thrown inside the decoder.

1 Like
Minor aside

FWIW, this isn't quite true — codingPath is exposed to also allow types to tell where they are in the encoding tree at a given point, and optionally apply different encoding rules if they need to. It's also helpful for debugging, but that's not the only use-case.

I'm actually surprised that codingPath would be a performance pitfall for the APIs; as far as I remember, nothing in the APIs mandates that codingPath be built eagerly, and it could very well get built lazily upon access.

(I think it's also important to try to separate the implementation of specific encoders and decoders from the API at large, but that's definitely not always easy, given the relatively small number of data points to work with.)

I would welcome breaking changes to:

  • the ownership model for improved ergonomics and control over performance
  • Codable, it's really quite cool, but it's time for something faster with fewer limitations

I would like to see AnyValue protocol in places where you wouldn't want a class to conform to protocol