Functor and Monad in Swift

Source: Internet
Author: User

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. mapallows 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 nilif 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.

mapthe 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. Tis 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 mapWith 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

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.