Swift. Functional Array

Swift. Functional Array

https://medium.com/@anatolytukhtarov/swift-functional-array-c2d063f96ef2

… almost :)

Swift brings lots of opportunities to iOS development. There are pretty useful functional hooks among them. Some people immediately associate functional programming with complex math and the M-word, but that’s not really the case. Functions are the real power in functional programming.

In this article we’ll see how to use some of them in order to make your daily Array routine comfortable, avoid code duplication and unnecessary typing just by using functions.

The problem

Reviewing a colleague of mine, we discussed the next function:

Simple computatios, right, but the flow is so nonlinear and nested. Is there a way to make it clear? Sure! But let’s start with basics :)

Functions

Any function in Swift has a signature:

(Type) -> (Arguments) -> Result

Here Type is a type declaring a function, Arguments is a tuple of arguments types and Result is a type function returns. Just try this line

let viewDidAppear = UIViewController.viewDidAppear

and check viewDidAppear’s type. (UIViewController) -> (Bool) -> Void, right? We often don’t see Type when we use functions, that’s because we call them directly on instances. Wait a minute…

Have you just said we assigned a function to a variable? Yes! Functions are the same first class values as any other types, like IntDate and many more. This means we can also pass them as arguments to other functions and return them as other functions’ result! Such functions are often called high-order functions. This feature’s value is just priceless.

Another important thing is that function’s arguments form a tuple.

What does all of these mean for us? Well, we can use Swift’s syntax sugar and type inference at full speed!

Photo by Tim Gouw on Unsplash

Sugar!

It’s time to go back to our snippet. In the line 2 we form a tuple for the function we’re about to use in the line 3. So, in the line 3 we can make code a bit sweeter just by realizing that offerCellReactorFactory arguments are tuple and instead of

self.offerCellReactorFactory($0.0, $0.1)

we can write

self.offerCellReactorFactory($0, $1)

That’s right! Tuple’s elements are separate arguments of your functions, so there’s no need for ugly dot notation in closures.

Type inference

This is where real magic happens. Type inference is language’s ability to automatically detect types of your expressions. We use it every day when declaring our variables, but it has a way more powerful and beautiful application.

Remember our UIViewController.viewDidAppear declaration? That viewDidAppear variable stores a function and Swift is able to understand what that function is. Cool, but there’s more.

First of all, Array’s map function takes a non-escaping closure as the argument, so there’s no need for [unowned self].

And now, watch the hands, the 3rd line becomes

.map(offerCellReactorFactory)

Ta-daaam! Here type inference did all the job for us. Swift is able to figure out all the types in this particular expression, since our tuple forms correct arguments for the function and map itself takes a function that transforms a tuple into something.

We just save ourselves lots of typing. This feature has so many advantages, starting from less typing and easy refactoring where it comes to renaming, and finishing with type correct expression. Hope, this also brings light on the question why type casts and force casts are bad.

Talking about renaming… let’s rename offerCellReactorFactory to makeOfferCellReactor :)

Pipeline

Take a look how we applied two map functions in the lines 2 and 3. Such application is called a pipeline.

Actually, one can call pipeline a series of functions applied to some value. Frankly, our problem is a pipeline application of functions. But this application is broken by variables declaration. Is this bad? No, not really, but Array is not just a plain type like Int.

Container type

Array is a container type. It usually encapsulates a number of elements and this number might change during computations. This is called a nondeterministic behaviour. In order to preserve nondeterminism Array type has specific properties. And they grant us that awesome pipeline application feature we’ve just described.

Did you notice that map returns another Array? This enables further application of functions like mapcompactMapappend and so on. Such chain application is common for container types. This means we can apply functions without breaking flow, one by one, describing our computations in a simple and clear manner.

Immutability

Let’s take a look at Array’s removeAll(where:) function. You may notice this is a point where our chain pipeline breaks. The problem is that this function mutates the value it operates on.

In modern software development mutability isn’t something wrong. Mutable data performs really good in speed, memory and many more, but unreasonable usage of mutable types has some painful consequences for a program as well as developers.

So, let’s implement our version removeAll(where:) in the map’s manner:

We moved lines 4 and 5 from the original source into a usable extension, so we can avoid duplication in further computations.

Result

Combining all the things we’ve learned, the result is simple, clear and functional

Just a simple note. Here OfferCellReactor implements Equatable protocol.

Photo by Ali Yahya on Unsplash

Conclusion

Code review is extremely powerful tool, as you see. Given a simple problem like concatenation of two arrays we’re able to discover multiple features in Swift. We discovered that tuple isn’t just another list structure, it’s an underlying type in Swift and has multiple applications. Any function’s arguments form a tuple. We can treat tuple members in closures just like any arguments.

Swift’s static type system enables type inference, a powerful feature that detects types for us. So we can pass functions in other functions without typing arguments and parameters’ names! In order to use this feature wisely we should give our functions clear names.

Array itself isn’t a plain type like Int. Array computations preserve nondeterministic behaviour of the type and enable pipeline functions application.

All these features allow us to do less typing and write clear, simple code. Since Swift isn’t a pure functional language, there’s no need and possibility to design the whole project in this manner. Functional hooks are just another powerful tool in your toolbox, so use them when you need them, not when you want to.

P.S.

Thanks for reading! One more thing :)

  • Yes, we can use type inference for
.map { (OfferViewModel(from: $0), String($0.ad.id ?? 0)) }

but this deserves a separate article.

  • We could have used filter instead of implementing our own function, but this example is more demonstrative.
  • Container type association for Array is just the simplest explanation of Monad I’ve ever met. So without deep knowledge and pretty abstract concepts we described pipeline application and its advantages.

Thanks to Oleksandr Leuschenko and Alice Zabolotnaya.

猜你喜欢

转载自blog.csdn.net/ultrapro/article/details/84584556