README.md

# Towel [![Build Status](https://travis-ci.org/knrz/towel.svg)](https://travis-ci.org/knrz/towel)

A delightfully simple Maybe monad library that's written for Elixir.

## Installation

Keep calm and add Towel to your `mix.exs` dependencies:

```elixir
def deps do
  [{:towel, "~> 0.1.0"}]
end
```

and run `$ mix deps.get`.

You're ready to start righting more expressive code!

## Philosophy

Let's keep it Elixir. Forget Haskell's `Just` and `Nothing`; Elixir's already got its tuple-based `{:ok, value}` and `{:error, reason}` construct, so let's use that. That's why Towel's `Maybe` monad returns `{:ok, value}` and `{:error, reason}` instead of `:some` or `:none` or whatnot.

Elixir's got piping (`|>`). Piping is great, but not when you've got wrapped values. Keep your towel on you:

```elixir
ok([answer: 42])
|> fmap(&Map.get(&1, :answer))
# {:ok, 42}
```

## Quick Start

What if you're going to run into an error along the way? Say you want to travel through a map, but aren't sure whether or not a key is going to have a value.

```elixir
map = %{system: "sol", z: %{z: %{nine: %{plural: %{z: %{alpha: "earth"}}}}}}
path = [:z, :z, :nine, :plural, :z, :alpha]

planet = List.foldl path, wrap(map), fn key, m ->
  fmap(m, &Map.get(&1, key))
end
# {:ok, "earth"}
```

 Woah! Did that just work?? Yes it did. But what if our path list was messed up and we didn't have a proper path? What if it was like this?

```elixir
map = %{system: "sol", z: %{z: %{nine: %{plural: %{z: %{alpha: "earth"}}}}}}
path = [:z, :z, :ten, :plural, :z, :alpha]

planet = List.foldl path, wrap(map), fn key, m ->
  fmap(m, &Map.get(&1, key))
end
# {:error, nil}
```

Look at that! You didn't even need a dolphin and your code safely ran without raising an error. Let's take a look at our functions list.

## Tour of the ~~Galaxy~~ Library

Note: When you `use Towel`, all the following functions are added to the global namespace.

### Maybe

**wrap/1** The Elixir version of Haskell's Monad.return. Whatever you pass into it, whether it be a pre-existing monad or a regular, unwrapped value, it will return a value wrapped in an `ok`. If `nil` or `:error` is passed into it, it will return an `error`.

```elixir
wrap(nil)    # {:error, nil}
wrap(:error) # {:error, nil}
wrap(true)   # {:ok, true}
wrap(false)  # {:ok, false}
wrap("mice") # {:ok, mice}
```

**unwrap/1** does the opposite—it'll give you your bare value, no matter how many times it may have been wrapped by a maybe.

```elixir
unwrap({:ok, {:ok, {:ok, 42}}}) # 42
unwrap(nil)                     # nil
unwrap({:error, "no point"})    # "no point"
```

**ok/1** wraps whatever you pass into it in an `ok`. For when you want to explicitly state that the wrapped value is, in fact, OK.

**error/0, error/1** wraps whatever you pass into it in an `error`. For when you want to explicitly state that there's a problem with the error.

**combine/1** takes a list of monads and reduces them into either `{:error, reason}` if there was an `error` in the list, or `{:ok, values}`, where `values` is a list of all the wrapped values, in the order they were passed in.

```elixir
case combine([network_request(params), db_request(query)]) do
  {:ok, [response, record]} ->
    # do stuff
  {:error, reason} ->
    Logger.debug("There was a problem. What kind of problem? I don't know.")
end
```

### Monad

**bind/2** takes a monad and a function, and applies the function with the wrapped value of the monad if the monad is `ok`. Silently passes if the monad is an `error`. Expects the return _value_ of the function to be either `{:ok, value}` or `{:error, reason}`. You can use `Maybe.ok` and `Maybe.error` instead.

```elixir
File.read("inspirational_quotes.txt")
|> bind(fn quotes ->
  if length(quotes) > 0 do
    ok(quotes)
  else
    error("life's depressing.")
  end
end)
```

### Functor

**fmap/2** takes a monad and a function, and applies the function with the wrapped value of the monad if the monad is `ok`. Silently passes if the monad is an `error`. Will automatically wrap the return value in a `maybe` by passing the return value to `Maybe.wrap`.

```elixir
File.read("inspiration_quotes.txt")
|> fmap(fn quotes ->
  if length(quotes) > 0, do: quotes, else: nil
end)
```

## Usage

Just add `use Towel` to the top of whatever module you're working in first (or just run it in `iex` to try it out):

```elixir
defmodule Hitchhiking.Galaxy do
  # Don't panic
  use Towel
end
```

## Is it any good?

Yes.

## License

Copyright (c) 2015 Kash Nouroozi

This work is free. You can redistribute it and/or modify it under the
terms of the MIT License. See the LICENSE file for more details.