What unites “currying”, “monads”, “algebraic data types”? Not only the fact that some developers are trying to circumvent these words, but also functional programming. Under the careful guidance of Evgeny Elchev, we plunged into a functional paradigm and understood almost everything. Don't be scared ahead of time, feel free to read the transcript of the tenth edition of the AppsCast podcast.
Daniil Popov: Hello everyone. Today, our guest is Evgeny Elchev from sunny Krasnoyarsk. Zhenya, tell me what you are doing and how you came to functional programming?
Evgeny Elchev: Hello everyone. I am an iOS developer at Redmadrobot, like everyone else, I paint buttons, sometimes I write business logic.
I first got acquainted with functional programming through articles. I, not quite understanding the point, thought it was kind of procedural programming, without classes. When I read one of the articles more closely, I realized that I was wrong and started digging. This is not to say that I just
came to functional programming, since real followers will lay bones for it and write to Haskell using monads wherever possible. I just plunged and use it only in production.
Daniil Popov: So, the monads have already gone.
Evgeny Elchev: Already difficult?
Daniil Popov: I tried to go the same way, but I opened the article, saw the words “currying”, “monad” and immediately closed it, thinking that I was not worthy yet. Do I have a chance?
Evgeny Elchev: Of course. You may not know this at all.
In simple words about functionalism
Daniil Popov: Let's give a simple definition for those who have never heard of a functional paradigm.
Evgeny Elchev: Everyone understands paradigms in their own way. If we take the explanation from Wikipedia, then this is the use of mathematical functions, where the entire program is interpreted as a mathematical function.
The functional approach (FP) is when you use functions in your work that have only input arguments and an output value. If the entire program consists of such functions, then this is a functional program.
Daniil Popov: OOP was a logical continuation of the usual procedural programming and solved the issue of encapsulating data in classes. What problems should functional programming solve?
Evgeny Elchev: Mathematicians invented functional programming. The guys gathered and decided to create a paradigm where everything can be proved. There is a code, it has not yet been launched, but we’ll prove it all. Any point of the program can be calculated, understanding where we will come when we allow some action.
It sounds abstract, so let's look at an example of a pure function. We write a sum function that takes two arguments, pass 2 and 3 to it, get 5, and we can prove it. It is always true. If our entire program consists of such functions, then it is all provable.
When creating languages, basic functions began to be missed, and additional features appeared: lambdas, higher-order functions, monads, monoids.
The functional paradigm does not solve a single problem, it is the same desire to write good code as simple as possible so that the programs are stable and easy to maintain.
If you look closely, many of the things we use in OOP are reflected in a functional approach. There are classes in OPP that encapsulate a set of fields. In FP, this can also be done using type classes. As Vitaly Bragilevsky likes
to say : “If you look at the tablet where the data goes along the lines and the function columns, then the FI goes along the columns, OOP goes along the lines.” That's all.
Daniil Popov: How does FI relate to other paradigms? Can I write functionally in OOP? How to mix paradigms, and does it make sense?
Evgeny Elchev: The paradigm is limited to the fact that you write functions with data. One of the features of AF is the absence of variable states. If your data is a class, then there is no problem. If the class is fully immutable, then it can be used. A class is simply a type, like a string or a number, only more complex, consisting of several values.
Daniil Popov: You said earlier that you can prove the mathematical correctness of a program if you write it exclusively functionally. Then the joke about “compiled - works” for functional languages ceases to be a joke, right?
Evgeny Elchev: If you look at I / O errors, then yes. Previously, programmers struggled with the problem: connected to the network, there is no network, nill returned, and everything fell. For the solution, the easiest way was to check what came - nill / not nill, but since there was a risk that not everything was taken into account, the program could compile and crash.
In modern languages, this is decided. In Haskell, you can write a program that will work and not crash, but no one will say how correctly it works. Of course, there are strict types, and you cannot make a mistake by adding a number to a string, but you can always leave bugs in the application, and it will work.
Functional Approach Place in Swift
Alexei Kudryavtsev: How much can Swift be called a functional language?
Evgeny Elchev: It is possible. Functionality is positioned as stateless, but you can write in Swift avoiding such states. At the same time, Swift is not the same as writing under iOS, where there are states everywhere. Of course, in Swift there are no special instructions like in Haskell, where all functions are clean by default and the compiler will not allow you to access the state and change it. If you mark the function as "dirty", then the changes become available.
Alexei Kudryavtsev: There was a pure modifier in the second or third Swift, but it acted only at the compilation level so that global values would not change. You wrote something in them, but the compiler cut everything out.
Evgeny Elchev: Yes, in iOS, the compiler will not follow this. Everything is entirely on our conscience: as you write, it will be so.
Alexei Kudryavtsev: You say that there are a lot of states in iOS applications, but where and what to do with them if you write in a functional style?
Evgeny Elchev: The most important state is the UI, for example, input fields. Practically nothing can be done with them. You can try to abstract from them, collect in one place and write as much code as possible without taking them into account. For example, you write one dirty function that gets all the data from the UI.
In my article I gave an example of an authorization form, where it is important that the user enters a username / password. We write one dirty function that returns a structure with authorization data, and then we write clean code on it. We got this data, validated, if the result is valid, send a request to the server. A server request is also a dirty function, and processing it entirely may be clean. “Received, parsed” is a linear function: to the input data, to the output is our structure. Then they were transformed, filtered and can be shown on the screen again.
Alexei Kudryavtsev : In Haskell, the compiler helps a lot. If state comes from somewhere, the whole call chain will be considered dirty and you need to wrap everything in monads. If the function is clean, then caching of the results works - the same output is always the same output. In Swift, you have to implement the maps yourself and try to return the result if it is already cached.
Daniil Popov: Most modern languages are considered multi-paradigm and many have functional features. For example, in Java there is a special annotation for the interface -
@FunctionalInterface
, which obliges the developer to define only one method in the interface, so that then this interface in the form of lambdas is used in the entire code. When adding a second method or deleting an existing one, the compiler will begin to swear that it has ceased to be a functional interface. Does Swift, apart from the iOS platform, have such functional features?
Evgeny Elchev: It's hard for me to understand what such an annotation does in Java. If you mean that you implement this interface to the class, and then you implement only one method, then there are no such restrictions in Swift. You can create typealias, name it and use it as a function type as an argument type, a variable type in order to assign a closure. You can define constraints - input and output closure arguments. The functions of a higher order that can take closures themselves are polymorphism, and in Swift you can build polymorphism on types, not limited to objects.
But I don’t know specific functional things. There used to be currying in the first Swift, but it was cut out. Now we can write a function for currying ourselves, or write a function so that it returns closures in one another, but this is not quite right.
We have no boxed functors or monads. They can’t even be written. New features in Swift 5.1 should help to do this, but I tried to write such code, and xCode crashed.
In principle, in Swift, if you wish, it’s easy to do everything yourself. There is already an optional monad out of the box (in Haskell - maybe). She has a map and flatmap for building linear computation.
Swift has powerful pattern matching. Switch, which exists in almost every language and in most cases associates an integer with a unit, can associate a variable with a specific pattern, ranges, types, extract values from related types. There is carthage - you compose a new type, passing several others into it. Based on them, you can also do pattern matching. There is an enumeration that can limit types, bind related types to them.
Alexei Kudryavtsev: I ’ll clarify that related types are similar to Kotlin’s sealed classes. This is the enum inside the case into which you can put the bound value. In switch, you can write: here is the case, expand, inside the object. For example, user and company cases with corresponding objects can be enum and can be switched. Only sealed classes are extensible, and switch is finite.
Why does the mobler need functionalism?
Daniil Popov: How is a functional approach useful for mobile development? Are there any problems that he solves?
Evgeny Elchev: There is no specific problem that can be solved with the help of functional programming.
The most important thing is that following these principles, even if it doesn’t work out, we must abandon states, because they are the main pain.
By abandoning them, you make your code more understandable. I am not saying that there will be fewer errors, because this must be at least measured. However, when you start to implement something, the code changes. It often happens that you look at the code and everything in it is the case, but you start to rewrite, swap, remove unnecessary and easier to read.
Following the functional paradigm, you get an additional source of inspiration.
Daniil Popov: If I start writing such immutable classes in the OOP language and using immutable methods, can I say that I write functionally?
Evgeny Elchev: Yes, while you begin to see the pros. It is becoming easier to test methods due to the lack of a global state, it is easier to compose a chain of calculations from methods.
Daniil Popov: In your article, you explain what a pure function and side effects are. You give an example with summation, where the function also modifies the external state. The problem is that when you read such code, it’s difficult to keep in mind all the changes: you need to look at this global variable, who else reads into it, who else writes to it what can happen. But the functional approach allows you to stay in the stream, not go to neighboring classes, you just read the code.
Alexei Kudryavtsev: If you are in a functional language, then on the one hand it’s easier for you to write code, but on the other, you have to understand what kind of monad you are in now.
Evgeny Elchev: Yes, but when you start writing everything on pure functions, other problems arise. For example, how to build a long chain of calculations. In the usual style, without thinking about it, you can easily dump data that was not originally available. In a functional approach, this cannot be done: you have to break up the chains, connect all the calculations used in several methods to states. You need to get used to it.
On the other hand, unlike classes in OPP, which make the code ossified and difficult to compose, functions can be more flexible. You can write one function, add freedom with the help of closure, throw such functions and combine them into chains.
Alexei Kudryavtsev: This is similar to the Unix ideology: there is a bash, terminal and you can transfer data from small programs that do one small action to others.
Daniil Popov: It reminded me of the Rx approach, where they write giant chains.
Evgeny Elchev: You are both right. And Unix-way is about it, and Rx is a fusion of the idea of binding and reactivity. In FP, we bind to the source of the event and in the calculation chain we change it, tying the result to the final state.
Daniil Popov: Are multi-paradigm languages good at all, how convenient and useful is it, that the language can do this and that?
Evgeny Elchev: If you follow any paradigm clearly, there will always be things that are inconvenient to do. There are things that are difficult to achieve in a functional style, for example, storing state and making a cache.
When it is possible to choose a tool that is more suitable for a specific task - this is cool.
You can create a class, inside it make several methods in a functional style and organize the code concisely in chains, or abandon the class completely, make the necessary functions and use them.
The downside is that there is a dilemma of choice and the more options, the more difficult it is to choose. It’s also becoming more difficult to understand: the more options, the more difficult it is to read the code.
About Monad Jam
Alexei Kudryavtsev: Back to functionalism, what is a monad?
Evgeny Elchev: I would call it a container into which you can combine the chains of calculations. The easiest way is a container to which you can apply the function and convert it to a new container with a changed value.
Imagine the box in which the strawberry lies, and there is a device that allows you to make jam from strawberries, but you can’t put a box of strawberries in it, you need to pour it out. Monads - this is the very thing that allows you to put a box into the device.
This is not state in the direct sense, since state is stored separately, but here is the context (box) with the value and you pass from one to another. This is the transfer of information from one calculation to another.
Alexei Kudryavtsev: It turns out that in a functional approach, in order to make jam, you need to get inside the box ...
Evgeny Elchev: The beauty is that you don’t have to climb into the box. You can throw a box.
Functionality for the elite?
Daniil Popov: There is an opinion that functional programming cannot be practiced without a doctorate in mathematics. Is it true?
Evgeny Elchev: This is not true. Knowledge of mathematics, of course, makes everything better, but I forgot mathematics after graduating from university and live normally. In fact, all these are tools that are embodied in languages in solving specific problems. They can be used without trying to prove mathematically. While you will be compiling an equation from a mathematical point of view, it will be faster and easier to throw a couple of lines of code by typing, and they will work.
Alexei Kudryavtsev: How much can a hobby for a functional approach interfere with product development? If part of the code is already written functionally, is there any difficulty in working with it?
Evgeny Elchev: Not at all. If you are not a maniac and you will not write a huge ecosystem with decorators, then you can use the same pattern matching.
It will be more difficult if you want to switch to a new element of functionalism. For example, the fifth Swift and the result monad recently appeared, you hadn’t used it before, but now you decided that everything will be on it. You take the query function to the network and write that its result is now result (either data or error), and you decide to combine with the next query, and there you have a separate closure with the value and error, and you need to rewrite it. I started writing like this in one place, woke up two days later, when I rewrote half the code, I also made new wrappers for libraries to integrate beautifully.
Where to begin?
Daniil Popov: What should a beginner read to understand functional programming?
Evgeny Elchev: We need to take a purely functional language, for example, Haskell and try it in practice. You take a textbook and do the simplest examples. Here you understand the approach - when there is no for, you cannot create a variable in which you can change the value. Personally, I once took the book “Learn Haskell in the name of good”, where everything is described in simple language. After that, you can start reading articles on the Internet: about how monads look in Swift, about algebraic data types. A couple of articles, and it becomes clear that this should not be afraid.
Daniil Popov : The most difficult thing is to break the paradigm in your own head.
Evgeny Elchev: No need to plunge sharply into functional programming. Many people think that they will both sit down and start writing functionally - this is wrong.
Alexei Kudryavtsev: The coolest thing I saw was a
course on Stepic by Haskell from Denis Moskvin . You start by adding a couple of numbers, and ending with wrapping the monads in monads. And if you want to completely break your mind, that is, the book
“The structure of the interpretation of computer programs” is a course in Lisp from simple examples to what you write a Lisp interpreter in Lisp.
If the primary fear of functionalism has passed, then take a look at the report of Vitaly Bragilevsky from the spring AppsConf. However, in the autumn season of AppsConf we will touch on topics no less interesting - the iOS community is looking forward to a report by Daniil Goncharov on Bluetooth reverse engineering , and android developers together with Alexander Smirnov will discuss current approaches to building animations