This article was first published in sohu product technology public number, now it is synchronously published in my nuggets, you can leave a message to discuss after reading.
Who is this article for?
This article is intended for developers who already have some foundation in Swift development, but are interested in functional paradigms. Of course, if you’re only interested in functional paradigms, I think this article is worth reading.
What is functional programming?
Functional Programming first, what is the term “Functional Programming”?
When I need to look up the definition of a technical term, my first instinct is to check Wikipedia:
In computer science, fucnitonal programming is a programming paradigm where programs are constructed by applying and composing fucntions.
The term “programming paradigm” is part of the programming paradigm that is used in programming. The term “programming paradigm” is part of the programming paradigm in programming.
Programming paradigms are a way to classify programming languages based on their features. Programming paradigm (programming paradigm) is a way to classify programming languages based on their own characteristics.
Wikipedia also summarizes the categories of common programming paradigms:
- imperative
- procedural
- object-oriented
- declarative
- functional
- logic
- mathematical
- reactive
So what exactly is a programming paradigm? We know that programming is engineering and its purpose is to solve problems. There are many ways to solve problems. Programming paradigms represent different ways to solve problems. If we are the creators of the programming world, then the programming paradigm should be the methodology by which we create the world. Therefore, I am very fond of the translation of programming paradigm in Taiwan.
Why do I emphasize what programming paradigms are and itemize them?
Because programming itself is abstract, and programming paradigms are actually how we abstract the world, I just want to use this concrete definition to show that ** function is a methodology in itself. ** So we don’t need to be afraid of it when we learn it. When we encounter concepts such as transparency, side effects, coiling, functors, monads, lazy evaluation, etc., we are just afraid of it because we are not familiar with it, just like when we learn object orientation: Inheritance, encapsulation, polymorphism, dynamic binding, messaging, etc., are all new to us, so when we get familiar with functional concepts, everything will fall into place. ** In the familiar object-oriented programming paradigm, we know the idea that everything is an object, while in the purely functional programming paradigm, we can say that everything is a function. In functional programming, functions are first-class citizens, so what is first-class citizen? It can be used as a parameter, as a return value, or as an assignment to a variable. This means that it has the same status as an Int, String, or Double. In other words, use it as a primitive type!
Different ideas are the difference in the methodology of creating the world. Here I will take states as an example. Maintaining states will greatly increase the complexity of the system, especially when there are many states, and the introduction of the concept of states will bring many complicated problems: State persistence, environment model, etc., while using the object-oriented programming paradigm, ** each state can be defined as an object, ** as in C# state machine implementation, in functional programming? ** It is mentioned in SICP that the state changes with time, so can the state be represented by F (t)? ** This is the idea of using a functional abstraction of state.
, of course, here is not to say that I can only use a programming paradigm, I also don’t preach function type has been good, but the master functional can let us in the time to provide more choices, to solve the problem more efficiently solve the problem, in fact, we solve the problem, created the world, will surely use many kinds of methodology is a variety of programming paradigm, in general, More modern programming languages support multi-paradigm programming. Here’s an example from RxSwift:
public class Observable<Element> : ObservableType {
internal init(a)
public func subscribe<Observer> (_ observer: Observer) -> Disposable where Element = = Observer.Element.Observer : RxSwift.ObserverType
public func asObservable(a) -> Observable<Element>}/ / observer
final internal class AnonymousObserver<Element> : ObserverBase<Element> {
internal typealias EventHandler = (Event<Element- > >)Void
internal init(_ eventHandler: @escaping EventHandler)
override internal func onCore(_ event: Event<Element>)
}
extension ObservableType {
public func flatMap<Source> (_ selector: @escaping (Element) throws -> Source) -> Observable<Source.Element> where Source : RxSwift.ObservableConvertibleType
}
extension ObservableType {
public func map<Result> (_ transform: @escaping (Element) throws -> Result) -> Observable<Result>}Copy the code
Observables and observers are abstracted into classes and add behaviors and responsibilities. This is the object-oriented paradigm. It implements the OberveableType protocol and extends that protocol by adding a number of default implementations, which are protocol-oriented paradigms; It implements map and flatMap methods. It can be said that Observable is a Monad and provides a large number of operators for use and combination, which is a functional paradigm. At the same time, Reactive frameworks are known to be Reactive frameworks, so they are Reactive paradigms……
What’s more, isn’t programming ability the embodiment of abstraction ability? So I think it is very necessary to master the function! So why is it important specifically?
In 1984, John Hughes wrote a famous paper, “Why Functional Programming Matters,” that answered our question.
Why is functional programming important?
Usually some articles on the Internet summarize its advantages: It has no assignment, no side effects, no control flow and so on, different they’re just for various keywords such as reference transparent, no side effect of explanation, sheet is the only lists a number of functional program * * what “no”, but did not say what it “yes”, so these advantages are not much conviction. And when we actually write a program, we can’t deliberately write a program that lacks an assignment statement or specifically references transparent **. This is not a measure of quality, so what really matters?
Mentioned in this paper, modular design is the key to the success of procedural design, this view has been widely accepted, but often overlooked, that is to write a modularized program to solve the problem, the programmer must first put the problem is decomposed into subproblems, and then to solve these problems and combine solution. How a programmer can break down a problem depends directly on how he can glue the solution together. The functional paradigm actually provides us with a very important glue that allows us to design smaller, simpler, more versatile modules and glue them together.
So what kind of glue does it provide? This paper introduces two types:
Bonding function: higher order function
The first of the two new kinds of glue enables simple functions to be glued together to make more complex ones.
Glue simple functions into more complex functions. The advantage of this is that the granularity of our modularity is finer, and there are more complex functions that can be combined. If I had to make an analogy, I think it’s like the building blocks of Lego:This aggregation is an aggregation of a generalized higher-order function and some specialized functions. Once such higher-order functions are defined, many operations can be written easily.
Bonding procedure: lazy evaluation
The other new kind of glue that functional languages provide enables whole programs to be glued together.
Another glue that functional languages provide is the ability to make programs stick together. Suppose we have a function like this:
g(f(input))
Copy the code
Traditionally, you have to compute F first and then g. This is done by storing the output of F in temporary files. The problem with this approach is that temporary files take up too much space and make gluing between programs impractical. The solution provided by functional languages is that f and G run in strict synchronization, starting f only when the G view reads input. This method of evaluation should run as little as possible and is therefore called “lazy evaluation “**. It makes it possible to modularize the program into a generator that produces a large number of possible solutions and a selector that selects the appropriate solutions.
You should read this paper if you have time, and in the paper, it talks about three examples: Newton-Rafson roots, numerical differentiation, numerical integration, and heuristic search, and uses functions to implement them, and it’s so wonderful that I’m not going to repeat the examples. Finally, LET me quote the conclusion of the paper:
In this article, we show that modularity is the key to successful programming. Programming languages that aim to increase productivity must support modular programming well. However, new scoping rules and chunking techniques are not enough — “modular” means more than “module.” Our ability to break down programs depends directly on our ability to stick solutions together. To assist in modular programming, programming languages must provide good adhesives. Functional programming languages offer two new kinds of adhesive-higher-order functions and lazy evaluation.
A date tree (example)
For this example, I refer to objC.io’s Functional Swift book on how to package filters functionally.
Core Image is a powerful image-processing framework, but its API is weakly typed — Image filters can be configured through key-value encoding, which makes it error-prone, so you can use types to avoid runtime errors due to these reasons. This means that we can encapsulate some basic filters ** filters, ** and implement aggregation between them. This is one of the adhesives that functional programming, described in the paper above, provides: simple functions can be aggregated to form complex ones.
To first determine our filter type, the function should take an image as an argument and return a new image:
typalias Filter = (CIImage) - >CIImage
Copy the code
Here’s a quote from the book:
We should choose our genres carefully. This is more important than anything else, because type drives the development flow.
You can then start defining functions to build in specific base filters:
/// sobel extract edge filter
func sobel(a) -> Filter {
return { image in
let sobel: [CGFloat] = [-1.0.1.-2.0.2.-1.0.1]
let weight = CIVector(values: sobel, count: 9)
guard let filter = CIFilter(name: "CIConvolution3X3",
parameters: [kCIInputWeightsKey: weight,
kCIInputBiasKey: 0.5,
kCIInputImageKey: image]) else { fatalError()}guard let outImage = filter.outputImage else { fatalError()}return outImage.cropped(to: image.extent)
}
}
/// color inversion filter
func colorInvert(a) -> Filter {
return { image in
guard let filter = CIFilter(name: "CIColorInvert",
parameters: [kCIInputImageKey: image]) else { fatalError()}guard let outImage = filter.outputImage else { fatalError()}return outImage.cropped(to: image.extent)
}
}
/// color change filter
func colorControls(h: NSNumber.s: NSNumber.b: NSNumber) -> Filter {
return { image in
guard let filter = CIFilter(name: "CIColorControls", parameters: [kCIInputImageKey: image, kCIInputSaturationKey: h, kCIInputContrastKey: s, kCIInputBrightnessKey: b]) else { fatalError()}guard let outImage = filter.outputImage else { fatalError()}return outImage.cropped(to: image.extent)
}
}
Copy the code
Direct bonding
Now that you have the basic components in place, you can stack the wood. If there is a filter that needs to extract edges first -> color inversion -> color discoloration, then we can implement the following:
let newFilter: Filter = { image in
return colorControls(h: 97, s: 8, b: 85)(colorInvert()(sobel()(image)))
}
Copy the code
There are some problems with this approach:
- Poor readability: no code as comments, no easy way to know the order in which filters are executed
- Not easy to expand: THE API is not friendly, and when adding new filters, you need to consider the order and parentheses, which is very error prone
Custom function bonding
First we solve the problem of poor readability, which is caused by using nested call methods directly. So we avoid nested calls and define the Combine method directly to combine filters:
func compose(filter filter1: @escaping Filter.with filter2: @escaping Filter) -> Filter {
return { image in
filter2(filter1(image))
}
}
// sobel -> invertColor
let newFilter1: Filter = compose(sobel(), colorInvert()) // Left combined
Copy the code
This is left associative, so readability is OK, but what if there are three filter combinations? What about four filter combinations? Are there so many methods to define? Coincidentally, some people actually do this:If you look at RxSwift, you’ll see that it combines multiple Observable functions:zip
, combineLastest
Each method cluster provides a composite method that supports multiple parameters, but this means we could do the same in this case, but it is clearly not the best solution.
If you use combine here’s a scheme of three filters:
let newFilter2: Filter = compose(compose(sobel(), colorInvert()), colorControls(h:97, s:8, b:85)))
Copy the code
The readability is ok, but it is still error-prone when adding new filters and not easy to expand. If multiple filters are to be combined, multiple Combine calls are required.
Custom operator adhesive
If corresponding to the field of mathematics, in fact, the combination of these filters is not + in the four operations? Layer upon layer of effects, of course, are more similar in effect to +, to be sure, but more consistent in property with subtraction -, all combine to the left, and none of them satisfy the commutative law.
So we can customize operators to handle filter combinations:
infix operator >>>
func >>>(filter1: @escaping Filter.filter2: @escaping Filter) -> Filter {
return { image in
filter2(filter1(image))
}
}
Copy the code
There is also a small problem that if there are three filter combinations, the error will be reported, because we did not specify how it is combined (left or right), so here we make it inherit the priority of addition, because it is left as well as addition:
infix operator >>>: AdditionPrecedence // Let it inherit the precedence of the + operator, left join
func >>>(filter1: @escaping Filter.filter2: @escaping Filter) -> Filter {
return { image in
filter2(filter1(image))
}
}
Copy the code
Let’s use it happily:
let filter = sobel() >>> colorInvert() >>> colorControls(h: 97, s: 8, b: 85)
let outputImage = filter(inputImage)
imageView.image = UIImage(ciImage: outputImage)
Copy the code
So here’s a summary of the process, assuming the need exists:
We have defined a lot of basic Filter layers, and then we must combine the basic filters to meet our actual needs. Some filters may have three basic Filter combinations, some may need five basic Filter combinations, and of course, in the extreme case, there may also need ten Filter combinations.
Therefore, we need to define the ** bonding function of different filter combinations. ** We have gone through three combination schemes altogether:
- Direct combination
- Defining the compose function
- Custom operators
Of course, you can also use a better combination plan, if you want to leave a message, discuss together.
There is also a date tree (example)
The following example, which is a common problem in Objective-C programming, requires that the second row of data must wait until the first row has finished before the request can begin.
So get started!
First let’s look at the easiest implementation:
@objc func syncData(a) {
self.statusLabel.text = "Synchronizing naruto data."
WebAPI.requestNaruto { (firstResult) in
if case .success(let result) = firstResult {
self.sectionOne = result.map { $0 as? String ?? "" }
DispatchQueue.main.async {
self.tableView.reloadSections([0], with: .automatic)
self.statusLabel.text = "Synchronizing one Piece data."
WebAPI.requestOnePiece { (secondResult) in
if case Result.success(let result) = secondResult {
self.sectionTwo = result.map { $0 as? String ?? "" }
DispatchQueue.main.async {
self.statusLabel.text = "Synchronized One Piece data successfully."
self.tableView.reloadSections([1], with: .automatic)
}
}
}
}
}
}
}
Copy the code
Familiar with? Of course, the second request is made directly in the callback of the first request, but note that this is not the same as OC writing. Are we different from writing and simple human translation machine? Are we writing Swift, a multiparadigm programming language?
Going back to the example, let’s go back to the case, and I think there are a few problems with writing this way:
- Data changes are coupled with UI changes
- Multiple nested
- It violates the Open Closed Principle (OCP) : it should be Closed to modification and Open to expansion
- Very ugly!
Address data and UI coupling
From the point of view of importance, I think problem 4 should be addressed first, but for the sake of pace, let’s start with problem 1
@objc func syncDataThere(a) {
// Nested functions
func updateStatus(text: String.reload: (isReload: Bool, section: Int)) {
DispatchQueue.main.async {
self.statusLabel.text = text
if reload.isReload { self.tableView.reloadSections([reload.section], with: .automatic) }
}
}
updateStatus(text: "Synchronizing naruto data.", reload: (false.0))
requestNaruto {
updateStatus(text: "Synchronizing one Piece data.", reload: (true.0))
self.requestOnePiece {
updateStatus(text: "Data synchronization succeeded", reload: (true.1))}}}Copy the code
Here I encapsulate both the network request and the data processing into the network request and use swift’s features: nested functions strip out some of the repetitive code so that the entire request is very clean and the data and UI are isolated and not coupled together.
But the nesting problem still exists, how to solve it?
Resolve multiple nesting
Remember the first date tree I introduced? I used custom operators to solve the nesting of function calls, which is the same idea here, but more complicated.
Here’s another quote I need to repeat from Functional Swift:
We should choose our genres carefully. This is more important than anything else, because type drives the development flow.
Step 1 Abstraction
There are two types that need to be abstracted. The first is the function that executes a single statement (in this case, updating the UI), and the second is the function that corresponds to the network request
infix operator ->> AdditionPrecedence
typealias Action =() - >Void
typealias Request = (@escaping Action) - >Void
Copy the code
Step 2 Abstraction
So how do you break down an old function into a function represented by a type?
func syncDataF(a) {
.
requestNaruto {
updateStatus(text: "Synchronizing one Piece data.", reload: (true.0))
self.requestOnePiece {
updateStatus(text: "Data synchronization succeeded", reload: (true.1))}})Copy the code
If we go from top to bottom, then the abstract process should be
(Request, Action) -> Request
The first Request and the first Action in the callback, but the first Request hasn’t finished yet, so the Request is still returned
(Request, Request) -> Request
The first Request for the first Action + the second Request is processed, but the Request is still not finished, so the Request is still returned
(Request, Action) -> Action
Second request plus final Action to be processed, over!
So here’s the result:
@objc func syncDataFour(a) {
func updateStatus(text: String.reload: (isReload: Bool, section: Int)) {
DispatchQueue.main.async {
self.statusLabel.text = text
if reload.isReload {
self.tableView.reloadSections([reload.section], with: .automatic)
}
}
}
updateStatus(text: "Synchronizing naruto data.", reload: (false.0))
// Let's break down the function: it is important to abstract the function
// (Request, Action) -> Request
// (Request, Request) -> Request
// (Request, Action) -> Action
// The method can be defined by this unpacking method
let task: Action =
requestNaruto
->> { updateStatus(text: "Synchronizing one Piece data.", reload: (true.0)) }
->> requestOnePiece
->> { updateStatus(text: "Data synchronization succeeded", reload: (true.1)) }
task()
}
Copy the code
The result? I solved the nesting problem, which was good, perfect, but also naive.
Solve OCP problems
Even if we use custom operators, we don’t solve the OCP problem, because if we add requests, we still need to modify the original method, which still violates the OCP rules.
So what’s the solution?
HMMMM, specific, ask everybody oneself to test!
I have added the corresponding reference information at the end of the article. This example is based on Weng Yang’s sharing Swift, Improve the design of existing code in the Swift Conference held in China in 2016. If you have time, I hope you can have a look at this sharing.
In the sharing, he used protocol-oriented thinking to solve the OCP problem, very abstract, very wonderful.
conclusion
I’m glad you’re here, and I think the energy density of this article shouldn’t waste your time.
In this article, I first asked about functional programming and the definition of programming paradigms, just to show that functional programming is complicated only because we are unfamiliar with it, and it should be a necessary tool.
Then I introduced my paper “Why Functional Programming Matters,” which illustrates Why Functional Programming is important and mentions the two weapons of the Functional paradigm: higher-order functions and lazy evaluation.
Finally, I used two date trees to show you the wonderful chemistry that Swift can have with the idea of functions.
So this is the end of Swift’s functional journey. However, I still want to add a few words. In fact, Swift has updated a lot of content in WWDC every year. Swift itself has also been adding new features and iterating steadily.
Finally, I quote a passage from Wang Anshi’s Tour of Zen Mountain:
And the world’s great, magnificent, very view, often lies in the risk far, and people rarely to how, so not aspirant can not also.
With you!
reference
- Wikipedia. “Functional programming”. (en.wikipedia.org/wiki/Functi…).
- Wikipedia. “Programming lot”. (en.wikipedia.org/wiki/Progra…).
- John Hughes. “Why Functional Programming Matters”.(PDF) (www.cs.rice.edu/~javaplt/41…)
- Objc. “Functional Swift”. (eBook) objccn. IO/products/fu…
- “Swift, Improving the design of existing code “.(Video)(www.youtube.com/watch?v=z4r…)
- “Swift Function Practice “.(Video)(www.youtube.com/watch?v=lf9…)
- ScottWlaschin. “The Functional ToolKit”. (Video) (www.bilibili.com/video/BV1ex)…