By Soroush Khanlou, translator: Joy; Proofreading: wax gourd; Finalized: CMB

In the past, I’ve written about many reasons to like Swift. But today, I want to write about the shortcomings of this language. This is a matter of penny-pinching, so I’m going to give examples of what the language does well, what it doesn’t do well, and what it promises.

Language definition VS non-language definition

Take a look at the Ruby language

Ruby’s attr_accessor is a method that defines setters and getters for instance variables. You can use it like this

class Person

attr_accessor :first_name, :last_name

end

Copy the code

At first glance, it looks like a language feature, similar to the way Swift’s let and var properties are declared. However, Ruby functions can be invoked even without parentheses, and this is only a class-scoped function (in Swift we will invoke a static function) :

def self.attr_accessor(*names)

names.each do |name|

Define_method (name) {instance_variable_get("@#{name}")} # This is the getter method

define_method("#{name}=") {|arg| instance_variable_set("@#{name}", arg)} # This is the setter

end

end

Copy the code

If you can’t read Ruby, that’s fine. It uses a function called define_method to create getters and setters for the variables you pass. In Ruby, @first_name means an instance variable named first_name.

That’s one of the reasons I fell in love with Ruby. They first designed their metadata toolset to create useful language features, and then used those tools to implement the language features they wanted. Yehuda Katz Explores how Ruby implemented this idea in its Blocks. Because Ruby’s language features are written using the same tools and in the same language, and all users have access to that language, users can also write language features within that language and in similar styles.

Optional type

A core feature of Swift is its optional types. It allows the user to define whether a variable can be null. In the system, the format defined as an enumeration:

enum Optional<WrappedType> {

case Some(WrappedType)

case None

}

Copy the code

Like attr_accessor, this feature uses a Swift language construct to define itself. This is great, because it means that users can create similar things using different semantics, like this fictional RemoteLoading type:

enum RemoteLoading<WrappedType> {

case Loaded(WrappedType)

case Pending

}

Copy the code

It has the same form as Optional, but has a different meaning. (In this blog post, Arkadiusz Holko expands on this enumeration.)

However, to the extent that Swift’s compiler knows about the Optional (Optional) type but not RemoteLoading (RemoteLoading), it lets you do something special. Take a look at these same statements:

let name: Optional<String> = .None

let name: Optional<String> = nil

let name: String? = nil

var name: String?

Copy the code

Let’s break down what they mean. The first statement is the complete statement (with type inference). You can declare your own RemoteLoading properties using the same syntax. The second statement uses the NilLiteralConvertible protocol to define the operations to be performed when you set this value to nil. While this syntax is fine for your own type access, using RemoteLoading is not quite right. This is the first language feature that makes C language developers feel more comfortable with Swift, and we’ll come back to it later.

In the third and fourth statements, the compiler starts using Optional types to allow us to write special code. The third statement uses an Optional shorthand T? . This is called syntactic sugar and allows you to write common code in a simple way. The last sentence is another syntactic sugar: if you define an optional type, but you don’t assign it any value, the compiler will infer that its value should be. None/nil (only true if it is a var variable).

The next two statements do not support custom types. The Optional type of the language, which can be defined by existing constructs in the language, ends with an exception of a particular type that is accessible only to the current type.

family

Swift is defined as a language “like in the C family” thanks to loops and For statements.

Swift for.. The in syntax is special. Any data structure that complies with SequenceType can be passed with a for.. In loop to iterate over. This means that I can define my own type values and declare them serialized, using for.. In loop to iterate over it.

Although the if statement and while loop work this way in Swift 2.2 via the BooleanType, this functionality has been removed in Swift 3. I can’t be like in for.. Define your own Boolean value that way in the in loop and then use it in the if statement.

There are two basic approaches to defining language features, both of which are present in Swift. The first is to create a meta-tool that can be used to define language features. The other is to define an explicit and concrete relationship between language features and language type values.

You could argue that values that conform to SequenceType are more useful than values that conform to BooleanType. However, Swift 3 has removed this feature completely, so you have to admit it: you’d have to think that BooleanType is so useless that it would be banned altogether.

The operator

Operators in Swift are also worth investigating. A syntax for defining operators exists in a language, and all arithmetic operators are defined in this syntax. Users are free to define their own operators, which is useful if you want to create your own BigInt and also want to use standard arithmetic operators.

Whereas the + operator is defined in the language, the ternary operator? : No. When you click +, the command jumps to the declaration of the operator. When you click on the? And:, but there was no reaction. If you want to use single question marks and exclamation marks as operators in your code, you can’t. Note that I’m not saying it’s not a good idea to use an exclamation point operator in your code. I’m just saying that this operator has been specially treated and hardcoded into the compiler, just like any other operator defined in C.

In all three cases, I compared two things: the useful syntax used by the library to implement features, and the special case where the privileged library trumps the user’s code.

The best grammars and syntactic sugar can be mined in depth by the authors of a language with their own types and systems. Swift sometimes uses NilLiteralConvertible, SequenceType, and BooleanType to address these situations. Var name: String? Being able to infer its default attribute value (.None) obviously doesn’t fit this condition, so it’s a less powerful syntactic candy.

Another thing I think is worth noting is that even though I love Ruby’s syntax, Ruby isn’t very flexible when it comes to operators and falsiness. You can define how to implement an existing operator, but you cannot add a new one, and the precedence of the operator is fixed. Swift is more flexible in this regard. And, of course, before Swift 3, Swift also had more flexibility in defining falsiness.

error

In a way, Swift’s optional type is similar to C’s controllability, and Swift’s error handling is similar to C’s exception handling. Swift’s error handling introduces some new keywords: do,try,throw,throws, rethrows, and catch.

Functions and methods that use the throws tag can return a value or throw an ErrorType. Thrown errors are caught in the catch blocks function. Behind the scenes, you can imagine that Swift overwrites the return value type of a function with a _Result type that may represent success or failure (like antitypical/Result), for example:

func doThing(with: Property) throws -> Value

Copy the code

Rewritten as

func doThing(withProperty) -> _Result<Value, ErrorType>

Copy the code

In fact, the _Result type is not explicitly defined, but is handled implicitly by the compiler. This doesn’t make much difference to our example.) Inside the calling function, a successful value is passed through the try statement, and an error occurs by jumping in and executing a catch block.

Compare this to the previous example, where language features are defined within the language, and add syntax (such as operators and SequenceType) and syntax sugar (such as Optional), and the code looks exactly as expected. In contrast, Swift’s error handling does not expose its internal _Result model, so users cannot use or change it.

There are situations where using the Swift model for error handling is appropriate, such as Brad Larson’s code for moving robot arms and my JSON parsing code. In other cases, the Result type and flatMap are more appropriate.

Other code might rely on asynchronous processing and want to pass a value of type Result to the Completion Block. Apple’s solution only works in certain situations, and giving greater freedom to error models can help narrow the gap between the language and its users. Result is great because it’s flexible enough to play around with. The try/catch syntax is not very compelling because it is strictly used and there is only one way to use it.

In the future

Swift 4 promises asynchronous language features in the near future. It’s not clear how this will be implemented, but Chris Lattner has written a lot about Swift 4

A class of concurrency: Actors, synchronization/wait, atomicity, memory models, and a few other related topics

What will Swift’s asynchronous processing mechanism look like? Asynchrony/wait is my main theory. To the uninitiated, asynchronous/wait declarations when is a function asynchronous

async Task<int> GetIntAsync()

{

return new Task<int>(() =>

{

Thread.Sleep(10000);

return 1

});

}



async Task MyMethodAsync()

{

int result = await GetIntAsync();

Console.WriteLine(result);

}

Copy the code

The first function method, GetIntAsync, returns a task that waits some time and then returns a value. Because this function returns a Task, it is marked async. The second function method first calls MyMethodAsync with the keyword await. This informs the entire system that it can do something else before completing the GetIntAsync task. Once this is done, the function resumes control and prints on the console.

From this example, the C# Task object looks a lot like Promise. Also, any function that uses await must be defined as async. The compiler makes sure of this. This solution is similar to Swift’s error-handling model: thrown function methods must be caught, and if not, they must also be tagged with throws.

Like the error-handling model, it has its flaws. With the addition of new constructs and some keywords, it’s more like syntactic sugar than a useful tool. This construction depends partly on the types defined in the standard library and partly on the syntax defined by the compiler.

attribute

Attribute behavior is another big feature that Swift 4 is likely to introduce. Here is the rejection proposal for attribute behavior, a feature that gets closer attention in Swift 4.

Attribute behavior lets you attach behavior to an attribute, such as adding lazy. The lazy property, for example, only sets its value when it is accessed for the first time. But you can now use this particular behavior, which is hardcoded directly into the Swift compiler. Attribute behavior makes it easier for the library to implement some behaviors while making it easier for users to fully customize behaviors.

Probably the best feature in the world. Start with a feature that has been hard-coded into the compiler, and after that feature has gained some cachet, create a more general framework that allows you to define that feature through the language itself. With this in mind, any Swift developer can implement similar functionality, fine-tuned to suit their needs.

If Swift’s error model follows the same path, Swift’s standard library might expose a value of type Result, and then any function that returns a Result value can, if necessary, Use do/try /catch syntax (like parallel, synchronized events that can fail individually). For errors that do not need to conform to the currently available syntax, like asynchronous errors, users will be able to use a common Result. This Result requires many chains, and the user can flatMap.

Asynchronous/wait can work the same way. Define a Promise or Task agreement, and abide by them, and the Task will be await. Then and flatMap are available to select language features based on user requirements.

metaprogramming

I want to write more about metaprogramming. I’ve written about metaprogramming in Objective-C, and it’s very similar to what we’re doing right now. The line between code and meta-code is blurry. The code in the Swift compiler is metacode, and Swift itself is code. If defining an implementation of an operator function (as you would with Ruby) is code, then defining a brand new operator looks like meta-code.

As a protocol-oriented language, Swift is unique in allowing us to tap into the syntactic charms of the language, much as we did with BooleanType and SequenceType. I’d love to take a look at these expanded capabilities.

The line between keyword stop and syntactic start or syntactic stop and syntactic sugar start is not clear, but engineers writing in this language should be able to use tools that develop standard libraries.

This article is translated by SwiftGG translation team and has been authorized to be translated by the authors. Please visit swift.gg for the latest articles.