神刀安全网

Functors, Applicatives, and Monads in Plain English

Let’s learn what Monads, Applicatives, and Functors are, only instead of relying on obscure functional vocabulary or category theory we’ll just, you know, use plain english instead .

Functors

Functors are containers you can call map on. That’s it. Seriously.

A million words of category theory and Haskell examples to say Functors contain stuff and if you apply a function to that stuff you get the same kind of container back out with the stuff inside it transformed.

Functors you already know and love include Optional and all the Swift standard library sequence types like Dictionary , Array , and Set .

To put it another way, a Functor applies functions to the values it contains, instead of to itself. Normally if I call some function doStuff(value: Int) it requires I pass an Int . If I have an Optional<Int> , doStuff has no clue what to do with that. But since Optional is a Functor it understands how to take doStuff and execute it on the value inside itself, then spit out a new Optional . You don’t lose the int-ness or optional-ness .

Know what else can be a Functor? Functions!

// hooray, functions are functors now. func map<T>(f1: (T) -> T, f2: (T) -> T) -> (T) -> T {     return { f1(f2($0)) } }

Swift can’t represent Functor in the type system at the moment because it doesn’t have "Higher-Kinded Types".

Applicatives

Applicative lets you stuff the function itself inside a container, otherwise it’s almost identical to Functors.

I told you we were going to use plain english.

Why? Because you may want to have a function in the container and apply it to value(s) in another container of the same kind. You could extend Optional with apply like this:

extension Optional {     func apply<U>(f: ((Wrapped) throws -> U)?) rethrows -> U? {         return try f.flatMap(self.map)     } }

(Some versions of the compiler require dual definitions because it doesn’t consider an optional throwing closure as a candidate for rethrows but I believe that bug is fixed.)

Swift doesn’t have a built-in definition for applicative because you can’t extend a protocol to say it returns the same container type but with a different element type.

Monad

Monads are containers you can call flatMap on. Again, that’s it.

Why? Because a monad takes special care to avoid double-containering (take that Oxford English Dictionary ). flatMap just says unwrap a value from it’s container, apply some function to it, then re-wrap it in a container. If the function spits out the same kind of container just use that instead of wrapping it in a second level container.

Once again, Optional is in fact a Monad. That’s because you can do this:

let value: Int? = 5 // <-- value is wrapped inside a container  // This function has no idea about containers, // it only wants Ints. It may or may not give us a String. func sayHello(x: Int) -> String? {     return arc4random() % 2 == 0 ? "hi there /(x)" : nil }  // That's OK, flatMap will unwrap value if not nil // and feed it to sayHello. We get a String? back out // so if value is nil we don't lose our optional-ness either. let greeting: String? = value.flatMap(sayHello)

We have to preserve the optionality of the result because if our input is nil the only valid thing we can do is return nil . We can’t sensibly cast nil to an Int and call sayHello anyway. flatMap saves us from gobs of boilerplate:

let value: Int? = 5 // <-- value is wrapped inside a container  // This function has no idea about containers, // it only wants Ints. It may or may not give us a String. func sayHello(x: Int) -> String? {     return arc4random() % 2 == 0 ? "hi there /(x)" : nil }  // Manually doing what flatMap does let greeting: String? switch value { case .Some(let wrapped):     //if we have a value      switch sayHello(wrapped) {     case .Some(let result):         //function produced a value         greeting = Optional<String>.Some(result)     case .None:         //function produced nil         greeting = Optional<String>.None     } case .None:      //value itself was nil     greeting = Optional<String>.None }

Conclusion

It’s nice to be able to preserve the "ness" of something, especially when you have containers with multiple states (like Optional) or values inside them (like Collections).

You can take a function that doesn’t understand arrays and feed it values one at a time, while still preserving the "array"-ness.

You can take a function that doesn’t understand optionals and feed it the wrapped value if there is one, while still preserving the optionality (which comes in handy if it happens to be nil ).

Monads are about taking stupid dumb functions that don’t have any clue about containers and applying those functions to the values inside the containers while preserving the containers in the result .

Update: @al_skipp pointed out my original flatMap example could be improved to better show how flatMap avoids double-wrapping values in the container. I’ve updated the post.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Functors, Applicatives, and Monads in Plain English

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址