I have been trying to teach myself functional programming since late 2013. Many of the concepts is very daunting because of their somewhat academic nature.
Since I ' m obviously not a expert, I intend this to be a very practical post. You'll find many posts trying to explain what a Monad are, some of them trying a bit too hard to come up with similes, BU T hopefully the sample code here'll illustrate some of the concepts better.
It wasn ' t until recently that I-finally could say that I-got what Monad means. Let's explore why this concept even exists, and how it can help you when writing Swift code.
Map
One of the first things that we got to see at the WWDC with the introduction of Swift is that we could map
use the function with the collection types. Let's focus on Swift ' s Array
.
Let numbers = [1, 2, 3]let doublednumbers = numbers.map {$ * 2}//Doublednumbers:2, 4, 6
The benefit of this pattern is so we can very clearly express the transformation that we ' re trying to apply on the list of elements (in this case, doubling their value). Compare this with the imperative approach:
var doubledimperative: [Int] = []for number in numbers {doubledimperative.append (number * 2)}//Doubledimperative:2, 4, 6
It's not on solving it in a one-liner vs 3 lines, but with the former concise implementation, there ' s a significantly h Igher signal-to-noise ratio. map
allows us to express "What do we want to achieve, rather than?" is implemented. This eases our ability to reason about code when we read it.
But map
doesn's is Array
map
a higher-order function so can be implemented on just any container Type. That's, any type, one-or another, wraps one or multiple values inside.
Let's look at another example: Optional
. are Optional
a container type that wraps a value, or the absence of it.
Let number = Optional (815) Let Transformednumber = number.map {$ * 2}.map {$ 2 = = 0}//transformednumber:optional. Some (True)
The benefit of IS, map
Optional
it would handle nil values for us. If we ' re trying to operate on a value nil
the May, we can use the Optional.map
apply those transformations, and end up with nil
if the original value nil
is, but without has to resort to nested to if let
unwrap the optional.
Let Nilnumber:int? = . Nonelet Transformednilnumber = nilnumber.map {$ * 2}.map {$ 2 = = 0}//transformednilnumber:none
From this we can extrapolate the when map
implemented on different container types, can has slightly different Behavi ORS, depending on the semantics of that type. For example, it is only makes sense to transform the value inside an when Optional
there ' s actually a value inside.
map
the signature of a method, when implemented on a Container
type, that wraps values of type T
:
Func map<u> (Transformfunction:t-U), container<u>
Let's analyze that signature by looking at the types. T
is the type of elements in the container, would be the type of the elements in the container that would U
Be returned. This allows us to, for example, map a array of strings, to a array of s that contains the lengths of each of the Int
s in the original array.
We provide a function that takes a T
value, and returns a value of type. Would then use the This U
map
function to CR Eate another Container
instance, where the original values is replaced by the ones returned by the transformFunction
.
Implementing
map
With our own type
Let's implement our own container type. A enum is a pattern so you'll see in a lot of Result
open source Swift code today. This brings several benefits to a API when used instead of the old obj-c nserror-by-reference argument.
We could define it like this:
Enum Result<t> {case Value (T) Case Error (Nserror)}
A implementation of a type known as in Either
some programming languages. Forcing one of the types NSError
to is an instead of being generic, since we ' re going-use it to r Eport The result of an operation.
Conceptually, is Result
very similar to Optional
: it wraps a value of a arbitrary type, which may or could be present. In this case, however, it could additional tell us why the value was not there.
To see an example, let's implement a function that reads the contents of a file and returns the result as a Result
object:
Func datawithcontentsoffile (file:string, encoding:nsstringencoding), result<nsdata> {var error:nserror? If let data = NSData (Contentsoffile:file, Options:. Allzeros, Error: &error) {return. Value (data)} else {return. Error (error!) }}
Easy enough. This function would return either an NSData
object, or a in case the NSError
file can ' t is read.
Like we do before, we may want to apply some transformation to the read value. However, like in the case before, we would need to check that we have a value every step of the the the-the-same, which may result in U Gly nested if let
s or switch
statements. Let's leverage like map
we did before. In this case, we'll only want to apply such transformation if we have a value. If we don ' t, we can simply pass the same error through.
Imagine that we wanted to read a file with string contents. We would get an NSData
, so then we need to transform into a String
. Then say this we want to turn it into uppercase:
NSData -> String -> String
We can do this with a series of map
transformations (we ' ll discuss the implementation of map
later):
Let data:result<nsdata> = datawithcontentsoffile (path, nsutf8stringencoding) let uppercasecontents:result< string> = data.map {nsstring (data: $ encoding:nsutf8stringencoding)!}. Map {$0.uppercasestring}
Similar map
to the early example with on Array
s, this code is a lot more expressive. It simply declares what we want to accomplish, with no boilerplate.
In comparison, the "What the" above code would look like without the use of map
:
Let data:result<nsdata> = datawithcontentsoffile (path, nsutf8stringencoding) var stringcontents:string?switch Data {case-let. Value (value): Stringcontents = NSString (Data:value, encoding:nsutf8stringencoding) case-let. Error (Error): Break}let uppercasecontents:string? = Stringcontents?. Uppercasestring
How would is Result.map
implemented? Let's take a look:
Extension Result {func map<u> (F:t-U)-result<u> {switch self}. Value (value): return result<u>. Value (f (value)). Error (Error): Return result<u>. Error (Error)}}}
Again, the transformation function f
takes a value of type T
(in the above example, NSData
) and returns a value of Type U
( String
). After calling map
, we'll get a Result<U>
() from a Result<String>
initial Result<T>
( Result<NSData>
). We only call f
whenever we start with a value, and we simply return another with the Result
same error otherwise.
Functors
We ' ve seen what map
can does when implemented on a container type, like Optional
, Array
or Result
. To recap, it allows us to get a new container, where the value (s) wrapped inside is transformed according to a function. So what's a Functor you ask? A Functor is any type, that implements map
. That's the whole story.
Once you know what a functor are, we can talk about some types like Dictionary
or even closures, and by saying that they ' re func Tors, you'll immediately know of something you can does with them.
Monads
In the earlier example, we used the transformation function to return another value, and if we wanted to use it to re Turn a new Result
object? Put another, what if the transformation operation so we ' re passing to can fail with a error as well map
? Let's look in what's the types would look like.
Func map<u> (F:t-U), result<u>
In our example, was an and T
NSData
we ' re converting into U
, a Result<String>
. So let's replace that in the signature:
Func map (F:nsdata-result<string>), result<result<string>>
Notice the nested Result
s in the return type. This is probably not the what we'll want. But it ' s OK. We can implement a function that takes the nested Result
, and flattens it to a simple Result
:
Extension Result {static func flatten<t> (result:result<result<t>>), result<t> {s Witch result {case let. Value (Innerresult): return Innerresult case let. Error (Error): Return result<t>. Error (Error)}}}
This flatten
function takes a nested Result
with a T
inside, and return a single Result<T>
simply by extracting the inner obj ECT inside Value
The, or the Error
.
A flatten
function can be found and other contexts. For example, one can an flatten
array of arrays into a contiguous, one-dimensional array.
With this, we can implement our Result<NSData> -> Result<String>
transformation by combining map
and flatten
:
Let Stringresult = Result<string>.flatten (Data.map {data:nsdata), (result<string>) in if let String = NSString (Data:data, encoding:nsutf8stringencoding) {return Result.value (string)} else {return Result<string>. Error (nserror (Domain: "com.javisoto.es.error_domain", Code:jserrorcodeinvalidstringdata, Userinfo:nil)})
This was so common, which you'll find this defined in many places flatMap
as or flattenMap
, which we could implement for Result
Li Ke this:
Extension Result {func flatmap<u> (F:t-result<u>) result<u> {return Result.flatt En (Map (f))}}
And with this, we turned our Result
type into a monad! A Monad is a type of Functor. A type which, along with map
, implements A&NBSP; FlatMap
function (sometimes also Known as bind
) with a signature similar to the one we've seen here. Container types like the ones we presented here is usually monads, but you'll also see this pattern for example in type s, encapsulate deferred computation, like Signal
or future
.
The words Functor and Monad come from category theory, with which I ' m not familiar at all. However, there's value in has names to refer to these concepts. Computer scientists love to come up with names for things. But it's those names that allow us to refer to abstract concepts (some extremely abstract, like Monad), and immediately kn ow what do we mean (of course, assuming we have the previous knowledge of their meaning). We get the same benefit out of sharing names for things like design patterns (decorator, factory ...).
It took me a very long time to assimilate all the ideas in this blog post, so if you ' re not familiar with any of this I do N ' t expect to finish reading this and immediately understand it. However, I encourage you to create a Xcode playground map
and try to come up with the implementation for, and flatten
Result
for or a similar container type (perhaps try Optional
with or even Array
), and use some sample values to play with t Hem.
And next time you hear the words Functor or Monad, don't be scared:) They ' re simply design patterns to describe common operations, we can perform on different types.
Open source version of the article, where you can create an issue to ask a question or open pull requests:https://github. Com/javisoto/blog-posts/blob/master/functor%20and%20monad%20in%20swift/functorandmonad.md
http://www.javiersoto.me/post/106875422394
Functor and Monad in Swift