/LVGMonads

Haskell-style Monads implemented in Swift

Primary LanguageSwiftMIT LicenseMIT

LVGMonads

CI Status Version License Platform

Haskell-style Monads implemented in Swift.

Overview

As a way to learn about Monads I've started implementing a few of them in Swift. The first is the IO Monad which is nearly complete. I hope to implement others over time. State, Writer and a version of Maybe (essentially Optional) are all in the back of my mind.

Each of the Monads will also define all of the Functor and Applicative typeclass operations.

Let Me Explain Monads to You Like No One Before

Just kidding. I am coming to love Monads, and category theory, and functional programming, but I am not the one to explain them. Writing my own versions of these Monads has helped me understand a lot, but I know enough to know that what I know is somewhat superficial. In lieu of my own version of the Monads-are-burritos tutorial I will just share some links to resources that have helped me.

Have something you think should be included? Create an issue with a link and I'll take a look at it.

Honestly, these ideas about Monads, category theory and the like only started to come together for me when I started writing my own versions of them. What do Lists, self-logging Writer functions, Maybe values, IO actions, and State transformations all have in common? They all seem so different. But they all share a common mathematical foundation, one that is just beginning to come into focus for me.

Operators

Some (most) of Haskell's standard operators are already defined by the Swift standard library but for other purposes. In Haskell, for example, >>= is the bind operator and >> is the sequence operator. Swift already defines both of those for use in bit-shifting operations. To avoid conflicts with the standard library, I've had to use operators that don't match up with the standard Haskell version. This is unfortunate, mainly because nobody likes to have to learn a whole new set of operators, but it is what it is.

Here is how they translate:

Haskell       LVGMonads     Name          Definition
--------------------------------------------------------------------------------------------
<$>           <^>           fmap          (<^>) :: Functor f => (a -> b) -> f a -> f b
<*>           <*>           apply         (<*>) :: Applicative f => f (a -> b) -> f a -> f b
>>=           =>>           bind          (=>>) :: Monad m => m a -> (a -> m b) -> m b
>>            ->>           N/A           (->>) :: Monad m => m a -> m b -> m b

There's another set of operators that aren't specific to the type class functions, but are used by the library for function composition and application:

Haskell       LVGMonads     Definition
---------------------------------------------------------------------
.             .<<           (.<<) :: (b -> c) -> (a -> b) -> (a -> c)
N/A           .>>           (.>>) :: (a -> b) -> (b -> c) -> (a -> c)
$             <--           (<--) :: (a -> b) -> a -> b
N/A           -->           (-->) :: a -> (a -> b) -> b

.<< and .>> are right-to-left function composition and left-to-right function composition, respectively. <-- and --> are right-to-left function application and left-to-right function application, respectively.

Accuse me of OCD if you want, but I made all of these 3 characters long because Xcode indents internal blocks of code 4 spaces. That means if you start a new line with one of these operators followed by a space followed by the expression, you're code will all naturally line up.

    aitch
    .<< gee
    .<< eff
    <-- ex    // evaluates to aitch(gee(eff(ex)))

Some of these operators could have been shorter, but then the spacing would be off and the world would end. There is a madness to my method.

Additional operators may be defined for each specific Monad, but the ones above are used in all of them.

The IO Monad

Introduction

The IO Monad represents an action that produces a side effect. It is a way of representing user input, UI updates, reading and writing to files, etc. It either receives data from the outside world or it alters the world in some (hopefully) meaningful way.

There are two main ideas from Haskell that I tried to implement: 1) IO actions are not executed when they are defined - execution is delayed until they are called by a parent IO function; and 2) It should not be possible to execute an IO action anywhere you want - there should be a parent-of-all-parent IO actions that calls all the others. The first was idea was easy to implement - take the action and wrap it in a closure that can be executed later (examples will follow). The second was a little more difficult.

In Haskell there is only one IO function that can ever be called - the main function. main starts the program and every other function in the program that returns an IO type has to be called by main, and main itself is a function that always returns IO (). In Haskell these rules are enforced by the language which has no mechanism for calling any IO function other than main (please correct me if I am wrong about this - I have a lot to learn about Haskell).

There is no way to enforce such a rule in Swift. I came up with a hack that makes you go out of your way to execute an IO action - I made it so that there is only one type of IO action that can be executed: IO<Main>. Main itself is just a dummy type:

public struct Main { public init() { } }

So the Swift equivalent of Haskell's main:: IO () is let main: IO<Main>. Of course unlike in Haskell you can put an IO<Main> action anywhere in your Swift code and execute it if you want to. In a way, each little IO<Main> action becomes a functionally pure program unto itself, which is kind of cool. After all, people are unlikely to start rewriting entire iPhone applications as a single IO<Main> action. But they may find places here and there where the Monadic structure of the IO type makes sense. The compositional nature of these Monadic structures make it possible to start very small and grow, naturally, into something very big.

Creating an IO action

The IO type is quite simple. This is all of it:

public struct IO<A> {
    
    /// The IO action to perform.
    let action: () -> A
    
    /// Initialize an IO object with a closure that contains an IO action.
    public init(_ action: () -> A) {
        self.action = action
    }
}

You can think of IO actions (oversimplification alert) as falling into one of two categories - read actions and write actions. An example of a read action might be getting a line of input from standard input. In Swift you use the readLine() function to do that, which returns a String?. We can turn it into an IO action like this (and we'll keep things simple for now by forcing it to return a non-optional String):

let ioReadLine: IO<String> = IO { readLine()! }

Now let's create a write operation. We'll print something to standard output:

let hello: IO<()> = { print("Hello world!") }

hello is fine, but it only ever prints one thing. Let's generalize it into a function that takes a String as input and returns an IO action that will print that String:

let ioPrint: String -> IO<()> = { s in IO { print(s) } }

Now we can make IO<()> actions by feeding Strings to ioPrint:

let startTheRumpus = ioPrint("Let the wild rumpus start!")

Composing IO actions

IO actions are composable. You can start small and build big. There are two operations for composing IO actions together, =>> and ->>.

The first, =>>, is pronounced bind it is the equivalent of the >>= operator in Haskell. Here is its definition:

public func =>> <A, B> (ioa: IO<A>, f: A -> IO<B>) -> IO<B>

It takes an IO<A> and a function of type A -> IO<B>, and returns an IO<B>. Let's use it to combine ioReadLine (an IO<String>) and ioPrint (a function of type String -> IO<()>):

let echo: IO<()> = ioReadLine =>> ioPrint

Now, whenever echo gets executed it will read a line from standard input and feed the String that it gets into ioPrint.

Note that echo can also be written like this:

let echo: IO<()> = ioReadLine =>> { x in ioPrint(x) }

This second technique is useful if you need to perform more complex operations on x before printing it:

let echo: IO<()> = ioReadLine =>> { x in ioPrint(x.uppercaseString) }

The second operator for combining IO actions is ->>. It doesn't have a name, per se, but I have taken to calling it then, so that a ->> b reads a then b. It is the same as the >> operator in Haskell. Like =>>, it combines two IO actions into one, but it ignores the output of the first IO action.

For example, echo is nice, but it needs a line of text to prompt the user to type something in to the terminal. Let's use ioPrint to prompt the user for some input before calling echo:

let betterEcho: IO<()> = ioPrint("Please enter some text:") ->> echo

Executing IO actions

Standing alone there is no way to execute any of these actions. Only IO actions of type IO<Main> can be executed. So how do we call them? Like this:

// This defines our main function:
let main: IO<Main> = ioReadLine =>> ioPrint =>> exit

What's happening here? The short version is that ioReadline grabs a String from standard input. That value is fed into ioPrint. ioPrint returns an IO<()> action which, when executed, returns a (). That () is fed into exit which is a special function available everywhere of type () -> IO<Main>. So when exit gets the () input from ioPrint, it returns IO<Main>.

But none of that happens right away. All we've done so far is compose a couple of IO actions into a single IO action and called it main. Nothing happens until we execute main, which we can do using the handy <= prefix operator:

// Execute the main function:
<=main

Only when main is executed do the other actions get executed, in the order in which they have been combined.

Installation

Once at least one Monad (IO is going to be first) is ready for release it will be available on CocoaPods. For now, if you want to fool around with it the best thing to do is to clone the repository and include the source files in your project. They are small.

Requirements

  • OS X 10.10 or later
  • iOS 8.0 or later

Author

letvargo, letvargo@gmail.com

License

LVGMonads is available under the MIT license. See the LICENSE file for more info.