Skip to content

Commit

Permalink
some cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
micmarsh committed Apr 18, 2015
1 parent 89f5fa0 commit c33dbe4
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 10 deletions.
16 changes: 8 additions & 8 deletions gist.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Purely Functional IO in Clojure

Clojure is an excellent functional programming language (I repeat myself), but it's not *pure*. Purity means every function returns the same result given the same input, just like mathematical equations.
Clojure is an excellent functional programming language, but it's not *pure*. Purity means every function returns the same result given the same input, just like mathematical equations.

Here's an example of a pure function in Clojure:
```clojure
Expand All @@ -11,7 +11,7 @@ And, because we're going to be talking about it a lot anyway, in Haskell:
```haskell
addEight = (+ 8)
```
No matter what we pass to each function, we'll always get the same result (or an exception, in Clojure's case). Now, let's add some impurity to the mix:
No matter what we pass to each function, we'll always get the same result. Now, let's add some impurity to the mix:
```clojure
(def print-plus-eight (comp println add-eight))
(def result (print-plus-eight 3)) ; prints "11"
Expand All @@ -32,15 +32,15 @@ In the Clojure example, `result` was of type `nil` because our function already

Haskell's type system makes this concept especially pervasive: every Haskell program has a `main` value, which must be something of type `IO ()`. If you need to do any impure operation, you'll need to compose your data structures so it all comes together at `main`. The static type checker will ensure that everything is in order before your program ever even compiles.

While Clojure and Haskell pretty sharply diverge in the "static checking" department, we can still get pretty darn close to having enforcably pure IO in Clojure. If you're as geeked out by this stuff as I am, read on!
While Clojure and Haskell pretty sharply diverge in the "static checking" department, we can still get pretty darn close to having purely functional IO in Clojure. If you're as geeked out by this stuff as I am, read on!

## The Data Structure

Since we'll be passing around some data, but eventually using it to peform an actual IO operation, it makes sense to define a protocol for our data types to implement:
Since we'll be manipulating data, eventually using it to peform an actual IO operation, it makes sense to define a protocol for our data types to implement:
```clojure
(defprotocol PerformIO (-perform-io [io]))
```
`-perform-io` will evaluate the given data structure, performing some side effects and returning a result. While it's going to play an important part in our monad implementation below, it's important to keep in mind that this doesn't necessarily have anything to do with monads, just our specific case.
`-perform-io` will evaluate the given data structure, performing some side effects and returning a result. While it's going to play an important part in our monad implementation below, it's essential to keep in mind that this doesn't necessarily have anything to do with monads, just our specific case.

Monads are, for our purposes, a "container" type for another value. Slightly more specifically, a monad is a monad because it defines two operations: one for "wrapping" values in a new instance of that monad, and another for "transforming" (in a pure, functional way), the value inside the monad into a new value.

Expand All @@ -50,7 +50,7 @@ With that in mind, let's use `clojure.algo.monad`'s `defmonad` to define a monad
[m-result (fn [v] (IOResult. v))
m-bind (fn [m f] (IOBind. m f))])
```
Althought I've deferred the actual work of the functions to two custom Clojure/Java types (which we'll get to in a second), you can see the two operations defined here:
Although I've deferred the actual work of the functions to two custom Clojure/Java types (which we'll get to in a second), you can see the two operations defined here:
* `m-result` (called `return` in Haskell) will wrap the given value `v` in an `io-m` monad
* `m-bind` (called `(>>=)` in Haskell), will use the function `f` to "transform" (again, purely functionally), the value inside the given monad `m`.

Expand All @@ -77,7 +77,7 @@ Much more exciting! To help figure out what's going on here, let's first let's l
* `Monad m =>` is saying "m must be a monad"
* The first argument, `m a`, is a monad that can contain any type
* this is the value `io` in `IOBind`'s constructor
* The argument, `(a -> m b)`, is a function that takes a value of type of `a`, and returns a function of type `b`
* The second argument `(a -> m b)`, is a function that takes a value of type of `a`, and returns a monad `m b`
* this is the function `f` in `IOBind`'s constructor
* `m b` is the return value of `>>=`, a new monad containing something of type `b`
* this is an instance of `IOBind`
Expand All @@ -104,7 +104,7 @@ Simple enough, right? Whatever code (presumably impure) you wrap inside of `as-i
```
`println'` takes a variable number of arguments, just like `println`, but returns an instance of `PerformIO`, a data structure that *represents* printing the arguments, rather than printing them right away.

`read-line'` is a great illustration of the "data structures for side effects" conconcept: it's not a function, because it doesn't have to be.
`read-line'` is a great illustration of the "data structures for side effects" concept: it's not a function, because it doesn't have to be.

Now, to put it all together:
```clojure
Expand Down
4 changes: 2 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
:url "https://github.com/micmarsh/pure-io"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/algo.monads "0.1.5"]])
:dependencies [[org.clojure/algo.monads "0.1.5"]
[org.clojure/clojure "1.6.0"]])

0 comments on commit c33dbe4

Please sign in to comment.