FUNCTIONAL PROGRAMMING
Everybody knows or have heard something about this paradigm and its benefits. I will try to explain the main concepts with functional design patterns examples by trying to give some nouns to generic parameters that make allusion to mathematical subsets and they are very hard to be understood (almost for me).
All the code examples are uploaded in a Playground in my Github account. The idea is to be improving the code and fixing bugs that could be found as I go being deep and increasing knowledge about this subject.
What is Functional Programming?
It is a declarative programming paradigm based on using mathematical functions without modifying the state of its elements.
Basic Principles
- High order functions (Lambda functions)
Are those functions that receive or return another function.
- Referential transparency
Use of pure functions. One function is pure if its return value is always the same for the same entry. (It has not collateral effects).
- Immutability
It must to be avoided to modify the state of the variables in our code.
- Lazy evaluation
It must to be avoided expressions evaluation if they are not necessaries yet. (Specially if they have a high computational cost).
- Recursion
Use of the iterative structures that the language provides. (filter, map, flatmap, reduce…) instead of using loops.
- Abstraction
Complex types decomposition in more simply types. Deletes the implementation dependencies in front of abstraction.
Basic concepts
Monoid: An element is a monoid if it meets the following premises:
- Intern binary operation. By combining two elements of the same type it is obtained other new element of the same type of the elements that have been combined.
- Associative property. It does not matter the order in which operation is performed.
- Identity element. If by performing the combination of an element with the identity element it is obtained the same element.
Functor: It is a mapeable element. It means that it implements map function.
Map function applies a transformation to an element and it returns other element by applying the transformation passed as argument.
1 2 3 4 5 6 7 8 9 |
import Foundation public protocol Functor { //MARK: Types associatedtype TypeParameter // it could be a function associatedtype TypeResult // it could be a function func map(function: ((TypeParameter) -> TypeResult)) -> TypeResult } |
(Currently this pattern is under revision and improvement process).
Monad: It is a structure that represents a calculation defined as a step sequence. It is a Monoid in the endofunctor category.
It nests functions of the same type and acts as the operator that links the results of each one of the functions.
1 2 3 4 5 6 7 8 |
import Foundation public protocol Monad: Functor { //MARK: Types associatedtype MonadType func flatmap(function: ((MonadType) -> MonadType)) -> MonadType } |
Currying: It is about decomposing a function that receives many parameters, in a sequence of functions that require an only parameter.
Functional design patterns
Typeclass: It is used to add functionality dynamically without need to use heritage. Composition (Ad-hoc Polymorphism).
It is usually composed by a protocol (it defines the functionality that is going to be added) and the element extension that has to implement this functionality.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import Foundation public protocol Addable { //MARK: Types associatedtype AddableType //MARK: Properties static var empty: AddableType { get } static func add(firstOperand: AddableType, secondOperand: AddableType) -> AddableType } extension Addable { static func times(element: AddableType, times: Int) -> AddableType { func execute(result: AddableType, times: Int) -> AddableType { switch times { case 0: return empty case 1: return result default: return execute( result: add(firstOperand: element, secondOperand: result), times: times-1 ) } } return execute( result: element, times: times ) } } |
Applicative: It is a kind of Functor, whose transform function to be applied when is executed the map instruction it is inside a context.
It provides a method to execute this function within the appropriate context.
It is usually used when the transform function can be optional. (ex: Validations).
1 2 3 4 5 6 7 8 9 10 |
import Foundation extension Optional { public func apply<ReturnType>(function: ((Wrapped) -> ReturnType)?) -> ReturnType? { switch function { case .some(let functionNotNil): return self.map(functionNotNil) case .none: return .none } } } |
Future: It is a design pattern that births to solve synchronization issues. It describes an element that acts as a proxy to get a result that has not been initialized completely.
It is composed by a map function and another function andThen, andThen function will be the responsible to concatenate the different operations than must be executed when previous operation has been end.
It eases error handling in a cleaner and readable way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
import Foundation public enum Result <Value, ErrorElement: Error> { case success(Info<Value>) case error(ErrorElement) } public struct Future<Value, ErrorElement: Error> { //MARK: Types public typealias ResultType = Result<Value, ErrorElement> public typealias Completion = (ResultType) -> () public typealias AsyncOperation = (_ completion: @escaping Completion) -> () //MARK: Properties private let operation: AsyncOperation public init(operation: @escaping AsyncOperation) { self.operation = operation } public init(value: Value) { self.init(result: .success(Info(value: value))) } public init(error: ErrorElement) { self.init(result: .error(error)) } public init(result: ResultType) { self.init(operation: { completion in completion(result) }) } public func start(completion: @escaping Completion) { self.operation() { result in completion(result) } } } // MAP extension Future { public func map<Element>(function: @escaping (Value) -> Element) -> Future<Element, ErrorElement> { return Future<Element, ErrorElement>(operation: { completion in self.start { result in switch result { case .success(let value): completion(.success(Info(value: function(value.value)))) case .error(let error): completion(Result.error(error)) } } }) } } // AND_THEN (FLAT_MAP) extension Future { public func andThen<Element>(function: @escaping (Value) -> Future<Element, ErrorElement>) -> Future<Element, ErrorElement> { return Future<Element, ErrorElement>(operation: { completion in self.start { result in switch result { case .success(let value): function(value.value).start(completion: completion) case .error(let error): completion(Result.error(error)) } } }) } } // ERROR Handling extension Future { public func mapError<ErrorTransformed>(function: @escaping (ErrorElement) -> ErrorTransformed) -> Future<Value, ErrorTransformed> { return Future<Value, ErrorTransformed>(operation: { completion in self.start { result in switch result { case .success(let valueInfo): completion(Result.success(valueInfo)) case .error(let error): completion(Result.error(function(error))) } } }) } } |
Reader: It is a design pattern that births to solve dependency injection issues. It is a functor, a monad and an applicative. It has (map, flatmap and apply methods).
Very useful to define multi environment systems.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import Foundation public class Reader<Environment, Type> { // MARK: Properties public let transform: (Environment) -> (Type) public init(transform: @escaping (Environment) -> (Type)) { self.transform = transform } public func apply(environment: Environment) -> Type { return transform(environment) } public func map<Result>(function: @escaping (Type) -> (Result)) -> Reader<Environment, Result> { return Reader<Environment, Result>{ environment in function(self.transform(environment)) } } public func flatMap<Result>(function: @escaping (Type) -> Reader<Environment, Result>) -> Reader<Environment, Result> { return Reader<Environment, Result>{ environment in function(self.transform(environment)).transform(environment) } } } |
Conclusions
Advantages
- Cleaner code.
- Code easily testable.
- More robust code.
- It provides solutions to main concurrence issues.
- It provides solutions to main dependency injection issues.
- It eases error handling.
- It opens the door towards reactive programming.
Disadvantages
- Swift is not a pure functional language and it does not have many of the needed tools to orientate the code to this paradigm by default.
- The examples and documentation are oriented to mathematical expressions and they are hard to be understood.
- The examples and documentation are available for pure functional languages.
- The learning curve is very high.
References
- www.swiftfuncional.com
- https://vimeo.com/113588389
- https://gist.github.com/mbrandonw/42651182eddf53ca3991
- http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures/
- http://alisoftware.github.io/swift/2015/10/17/lets-talk-about-monads/
- https://www.47deg.com/presentations/2016/01/27/functional-programming-patterns/
- http://www.danishin.com/article/Typeclass_in_Swift_and_Scala
- https://github.com/FineCinnamon/Katz/tree/master/katz/src/main/kotlin/katz/typeclasses
- https://news.realm.io/news/altconf-michael-gray-futures-promises-gcd/
- https://www.skilled.io/u/thomasvisser/building-a-simple-future-library-in-swift
- http://www.russbishop.net/monoids-monads-and-functors
- http://typelevel.org/cats/typeclasses.html
- https://github.com/alfonsomiranda/Functional-Programming
- https://github.com/Boris-Em/Swift-monad-Maybe-Reader-and-Try
- http://blog.ploeh.dk/2012/05/25/Designpatternsacrossparadigms/
- http://www.javiersoto.me/post/106875422394
- https://news.realm.io/news/swift-summit-javier-soto-futures/
- https://wiki.haskell.org/Functor-Applicative-Monad_Proposal
- http://www.nurkiewicz.com/2016/06/functor-and-monad-examples-in-plain-java.html
- https://github.com/FutureKit/FutureKit
- https://medium.com/@robringham/promises-in-swift-66f377c3e403
iOS Developer born in Vitigudino, Salamanca. Madrid is my adoptive city. Addicted to sports and electronic music