Functor and Monad in Swift

Functor and Monad in Swift,第1张

概述I have been trying to teach myself Functional Programming since late 2013. Many of the concepts are very daunting because of their somewhat academic nature. Since I’m obviously not an expert, I intend

I have been trying to teach myself Functional Programming since late 2013. Many of the concepts are very daunting because of their somewhat academic nature.

Since I’m obvIoUsly not an expert,I intend this to be a very practical post. You will find many posts trying to explain what a Monad is, some of them trying a bit too hard to come up with similes,but hopefully the sample code here will 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 2014 WWDC with the introduction of Swift was that we Could use the map function with the collection types. Let’s focus on Swift’s Array.

let numbers = [1,2,3]let doublednumbers = numbers.map { 
var doubledImperative: [Int] = []for number in numbers {    doubledImperative.append(number * 2)}// doubledImperative: 2,6
* 2 }// doublednumbers: 2,4,6

The benefit of this pattern is that 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:

map

It’s not about solving it in a one-liner vs 3 lines,but with the former concise implementation,there’s a significantly higher signal-to-noise ratio. map allows us to express what we want to achIEve,rather than how this is implemented. This eases our ability to reason about code when we read it.

But Array doesn’t only make sense on mapOptional is a higher-order function that can be implemented on just any container type. That is,any type that,one way or another,wraps one or multiple values insIDe.

Let’s look at another example: Optional

let number = Optional(815)let transformednumber = number.map { map * 2 }.map { Optional % 2 == 0 }// transformednumber: Optional.some(true)
 is a container type that wraps a value,or the absence of it.

nil

The benefit of Optional.map in nil is that it will handle nil values for us. If we’re trying to operate on a value that may be nil,we can use if let to apply those transformations,and end up with 

let nilNumber: Int? = .Nonelet transformednilNumber = nilNumber.map { map * 2 }.map { Optional % 2 == 0 }// transformednilNumber: None
 if the original value was map,but without having to resort to nested Container to unwrap the optional.

T

From this we can extrapolate that 

func map<U>(transformFunction: T -> U) -> Container<U>
,when implemented on different container types,can have slightly different behaviors,depending on the semantics of that type. For example,it only makes sense to transform the value insIDe an T when there’s actually a value insIDe.

This is the general signature of a U method,when implemented on a Int type,that wraps values of type String:

T

Let’s analyze that signature by looking at the types. U is the type of elements in the current container, map will be the type of the elements in the container that will be returned. This allows us to,for example,map an array of strings,to an array of Containers that contains the lengths of each of the transformFunctions in the original array.

We provIDe a function that takes a map value,and returns a value of type Result

enum Result<T> {    case Value(T)    case Error(NSError)}
 will then use this function to create another Either instance,where the original values are replaced by the ones returned by the NSError.

Implementing Result with our own type

Let’s implement our own container type. A Optional enum is a pattern that you will see in a lot of open source Swift code today. This brings several benefits to an API when used instead of the old Obj-C NSError-by-reference argument.

We Could define it like this:

Result

This is an implementation of a type kNown as 

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!)    }}
 in some programming languages. Only in this case we’re forcing one of the types to be an NSData instead of being generic,since we’re going to use it to report the result of an operation.

Conceptually, NSError is very similar to if let: it wraps a value of an arbitrary type,that may or may not be present. In this case,however,it may additional tell us why the value is not there.

To see an example,let’s implement a function that reads the contents of a file and returns the result as a switch object:

map

Easy enough. This function will return either an NSData object,or an String in case the file can’t be read.

like we dID 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 way,which may result in ugly nested 

NSData -> String -> String
s or map statements. Let’s Leverage map like we dID before. In this case,we will 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 

let data: Result<NSData> = dataWithContentsOffile(path,NSUTF8StringEnCoding)let uppercaseContents: Result<String> = data.map { Nsstring(data: map,enCoding: NSUTF8StringEnCoding)! }.map { Array.uppercaseString }
,that then we need to transform into a map. Then say that we want to turn it into uppercase:

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

We can do this with a serIEs of Result.map transformations (we’ll discuss the implementation of 

extension Result {    func map<U>(f: T -> U) -> Result<U> {        switch self {            case let .Value(value):                return Result<U>.Value(f(value))            case let .Error(error):                return Result<U>.Error(error)        }    }}
 later):

f

Similar to the early example with T on NSDatas,this code is a lot more expressive. It simply declares what we want to accomplish,with no boilerplate.

In comparison,this is what the above code would look like without the use of U:

String

How would map be implemented? Let’s take a look:

Result<U>

Again,the transformation function Result<String> takes a value of type Result<T> (in the above example, Result<NSData>) and returns a value of type f (Result). After calling map,we’ll get a Optional(Array) from an initial Result (map). We only call Dictionary whenever we start with a value,and we simply return another Result with the same error otherwise.

Functors

We’ve seen what map can do when implemented on a container type,like 

func map<U>(f: T -> U) -> Result<U>
T or NSData. To recap,it allows us to get a new container,where the value(s) wrapped insIDe are transformed according to a function. So what’s a Functor you may ask? A Functor is any type that implements U. That’s the whole story.

Once you kNow what a functor is,we can talk about some types like Result<String> or even closures,and by saying that they’re functors,you will immediately kNow of something you can do with them.

Monads

In the earlIEr example,we used the transformation function to return another value,but what if we wanted to use it to return a new 

func map(f: NSData -> Result<String>) -> Result<Result<String>>
 object? Put another way,what if the transformation operation that we’re passing to Result can fail with an error as well? Let’s look at what the types would look like.

Result

In our example, Result is an 

extension Result {    static func flatten<T>(result: Result<Result<T>>) -> Result<T> {        switch result {            case let .Value(innerResult):                return innerResult            case let .Error(error):                return Result<T>.Error(error)        }    }}
 that we’re converting into flatten,a Result. So let’s replace that in the signature:

T

Notice the nested Result<T>s in the return type. This is probably not what we’ll want. But it’s OK. We can implement a function that takes the nested Value,and flattens it into a simpleError:

flatten

This flatten function takes a nested Result<NSData> -> Result<String> with a map insIDe,and return a single flatten simply by extracting the inner object insIDe the 

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))    }})
,or the flatMap.

flattenMap function can be found in other contexts. For example,one can Result an array of arrays into a contiguous,one-dimensional array.

With this,we can implement our 

extension Result {    func flatMap<U>(f: T -> Result<U>) -> Result<U> {        return Result.flatten(map(f))    }}
 transformation by combining Resultand map:

flatMap

This is so common,that you will find this defined in many places as bind or Signal,which we Could implement for Future like this:

map

And with that,we turned our flatten type into a Monad! A Monad is a type of Functor. A type which,along with flatMap,implements a Result function (sometimes also kNown asOptional) with a signature similar to the one we’ve seen here. Container types like the ones we presented here are usually Monads,but you will also see that pattern for example in types that encapsulate deferred computation,like Array or .

The words Functor and Monad come from category theory,with which I’m not familiar at all. However,there’s value in having 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 kNow what 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 don’t expect you to finish reading this and immediately understand it. However,I encourage you to create an Xcode playground and try to come up with the implementation for ,  and  for  or a similar container type (perhaps try with  or even ),and use some sample values to play with them.

And next time you hear the words Functor or Monad,don’t be scared :) They’re simply design patterns to describe common operations that 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全部内容,希望文章能够帮你解决Functor and Monad in Swift所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1024309.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-23
下一篇2022-05-23

发表评论

登录后才能评论

评论列表(0条)

    保存