# Applicative programming in Ruby: railway reimagined

**Part 1 | Part 2**

In this post we will see how applicative programming can be used for implementing code in the Railway style using a gem applicative-rb.

Railway programming is a common patten for scenarios when you have a sequence of steps, which are executed in a given order. When everything is good—you get a successful result, if one step fails—you don’t perform the remaining ones and return the failure. In other words—it’s a fancy way of error–handling.

Imagine a service that process an order:

- we deduct money from user account;
- we check that the required item is in the stock;
- we update order status.

This is how we can do it with a DSL similar to well–known dry-monads:

```
class ProcessOrder
def initialize(order) = @order = order
def perform
result = ApplicationRecord.transaction do # start the transaction
deduct_from_user_account.bind {
prepare_shipment.bind { # we get here only when previous step returned success
update_order_status
}
}.tap do |result|
# rollback in case of failure
raise ActiveRecord::Rollback.new(result.failure) if result.failure?
end
end
end
private
def deduct_from_user_account
if @order.user.balance > @order.amount
@order.user.deduct_amount(@order.amount)
# we return success without explanation
Right()
else
# we return failure with the error message inside
Left("cannot deduct #{@order.amount}, user has #{@order.user.balance}")
end
end
def prepare_shipment
@order.item_id == 42 ? Right() : Left("not enough items in warehouse")
end
def update_order_status
@order.processed!
Right()
end
end
```

To make things work each *step* should return either success (`Right`

) or failure (`Left`

). The behavior itself depends on the container: the decision what to do is made inside the `#bind`

method.

## Container Either

Let’s step away from the service object for now and focus on the container. The container is called Either, and it can be implemented in the following way:

```
class Either
class Left < Either
attr_reader :error
def initialize(error) = @error = error
def deconstruct = [@error]
end
class Right < Either
attr_reader :value
def initialize(value) = @value = value
def deconstruct = [@value]
end
end
```

`Right`

means a successful result and might hold some value, `Left`

means some failure with the error message inside. As you see, it’s very similar to well–known `Maybe`

, but can hold a value inside.

Let’s see it in action, `fetch_email`

is a method to get the email by user ID:

```
def fetch_email(user_id)
if user_id == 42
Either::Right.new("john.doe@example.com")
else
Either::Left.new("User #{user_id} not found")
end
end
def format_email(either_email)
case either_email
in Either::Right(email) # here we unpacked email string from the container
Either::Right.new("Email: #{email}") # here we pack new string back to the container
in left
left # return container as is
end
end
format_email(fetch_email(42)) # => #<Either::Right:… @value="Email: john.doe@example.com">
format_email(fetch_email(1)) # => #<Either::Left:… @error="User 1 not found">
```

How we work with the value inside the container? You have to unpack it if possible (there’s no need to change the error value), work with value and pack back.

Looks like there will be a lot of code where we pack and unpack things! Can we avoid it?

## Functors

There is a nice abstraction for it called *functor*:

```
module Functor
# (a -> b) -> f a -> f b
def fmap(&_fn) = raise NotImplementedError
end
```

Functor interface has one function, we will call it `fmap`

, like it’s called in Haskell. Ruby does not have types, so we cannot add a signature to the code. Let’s use a Haskell notation, since it’s pretty readable `(a -> b) -> f a -> f b`

.

In other words, it should pass the function that transforms a value of type `a`

to a value of type `b`

. `#fmap`

will call this function on the data in the container, which has a type `f a`

(`f`

is “functor”). As a result, a value of type `f b`

will be returned.

Read more about Functors in my Haskell post

Here is the example implementation for Either:

```
class Either
# ...
include Functor
def fmap(&fn)
case self
in Functor::Right(value) then Functor::Right(fn.curry.(value))
in left then left
end
end
end
```

With this function `format_email`

can be simplified:

```
def fetch_email(user_id)
if user_id == 42
Either::Right.new("john.doe@example.com")
else
Either::Left.new("User #{user_id} not found")
end
end
def format_email(either_email) = either_email.fmap { |email| "Email: #{email}" }
```

Proper functor should follow some rules:

- If
`identity`

(`def identity(value) = value`

) is passed, than`fmap`

should return value without changes; `fmap (f . g) == fmap f . fmap g`

, where`.`

is a composition of functions.

## Applicative functors

This approach works great for functions with only one argument. But what if we two arguments or more? Thanks to curry, functions can take less arguments and return another function:

```
def sum(x, y) = x + y
Either::Right.new(42).fmap(&method(:sum))
# => #<Either::Right:... @value=#<Proc:... (lambda)>>
```

We can call this function with another argument and get the result:

```
Either::Right.new(42).fmap(&method(:sum)).value.(1) # => 43
```

How to make it more readable?

Applicative functor interface is the extension of `Functor`

and contains two more methods:

`pure`

that returns*most simple*container with value;`^`

takes the function from the first container and applies it to the value stored in the second container.

Applicative functors also have some laws, but they are a bit more complex so we will not discuss them here. Let’s assume that all the implementations in the post are valid.

This is how interface looks like:

```
module Applicative
include Functor # applicative functor should have same methods as functor
def self.included(klass)
# a -> f a
klass.extend(Module.new do
def pure(_value) = raise NotImplementedError
end)
end
# a -> f a
def pure(value) = self.class.pure(value)
# f (a -> b) -> f a -> f b
def ^(_other) = raise NotImplementedError
end
```

Let’s try to use it for `Either`

:

```
class Either
# ...
include Applicative
def self.pure(value) = Right.new(value)
def ^(other)
case self
in Right(fn) then other.fmap(&fn)
in left then left
end
end
end
```

Note that things will happen only if both containers are `Right`

, otherwise it will just keep the error.

Let’s rewrite `format_email`

one more time:

```
def format_email(either_email)
add_label = lambda { |label, email| "#{label}: #{email}" }
Either.pure(add_label) ^ Either::Right.new("Email") ^ either_email
end
```

Why is it useful? It makes `curry`

“safe”, because we can describe a “golden path”. Error will be propagated because of the container *semantics*: we agreed that `Left`

should be kept as is without changes. If you have some steps connected with `^`

and one of them returns `Left`

, all steps to the right won’t even happen and the `Left`

will be returned.

If it’s stilly blurry—check out this post with pictures

However, there is a small downside: each argument for the function we want to curry safely (`Either.pure(add_label)`

) should be calculated independently, because these calculations cannot see each other. This is what monads were invented for.

## Monads

We discussed monads briefly in the beginning, and I could not stop myself from implementing monads from scratch. We are not going to dig dip into the theory, and jump right to the practice.

Unlike applicative functors, monads can access the data form the previous steps. In order to make something monad you need to implement only two methods `return`

and `bind`

:

```
module Monad
include Applicative # monad should have same methods as applicative functor
def self.included(klass)
klass.extend(Module.new do
# a -> m a
def returnM(value) = pure(value)
end)
end
# m a -> (a -> m b) -> m b
def bind(&fn) = raise NotImplementedError
end
```

Check out the source here. As you see, `returnM`

does the same thing as we did in `Applicative#pure`

, while `bind`

is more interesting: it accepts the block, calls it with the current value in the container (if it makes sense, as usual), and returns the result.

We will go with

`returnM`

cause`return`

is a reserved word in Ruby

Compare signatures of `Applicative#^`

and `Monad#bind`

:

`Applicative#^`

:`f (a -> b) -> f a -> f b`

`Monad#bind`

:`m a -> (a -> m b) -> m b`

.

Note the difference: in the `Applicative#^`

we applied a function inside the container to the value inside the container, while in `Monad`

we pass the function to transform the unpacked value of type `a`

to the value of type `b`

packed to the container `m`

.

Let’s see how to do it for `Either`

:

```
class Either
include Monad
def bind(&fn)
case self
in Right(value) then fn.(value)
in left then left
end
end
end
```

Let’s change `fetch_email`

: now it can also return the invalid email, so we need to validate it before formatting:

```
def fetch_email(user_id)
case user_id
when 42 then Right("john.doe@example.com")
when 666 then Right("invalid")
else Left("User #{user_id} not found")
end
end
def validate(email)
email.include?("@") ? Either::returnM(email) : Left("invalid email")
end
def format_email(email) = Right("Email: #{email}")
```

We can write a function that fetches and validates the email using the monad interface of `Either`

:

```
def fetch_validate_and_format(user_id)
fetch_email(user_id).bind { |email|
# we get here only if `fetch_email` returned Right,
# but email is a String, not Either!
validate(email).bind { |validated_email|
format_email(validated_email)
}
}
end
fetch_validate_and_format(42) # => #<Either::Right:… @value="Email: john.doe@example.com">
fetch_validate_and_format(666) # => #<Either::Left:… @error="invalid email">
fetch_validate_and_format(1) # => #<Either::Left:… @error="User 1 not found">
```

Monads are kind of extension for Applicatives, and give us more features. Can we use them always? Not really, because it’s possible to create more applicatives than monads (check out this article to learn more on that).

## Service object in applicative style

Let’s get back to the service objects. This is what we had in the beginning of the post:

```
class ProcessOrder
include Dry::Monads[:result]
def initialize(order) = @order = order
def perform
ApplicationRecord.transaction do
deduct_from_user_account.bind {
prepare_shipment.bind {
update_order_status
}
}.tap { |result|
raise ActiveRecord::Rollback.new(result.failure) if result.failure?
}
end
end
private
def deduct_from_user_account
# ...
end
def prepare_shipment
# ...
end
def update_order_status
# ...
end
end
```

Note that all three actions are completely independent, which makes it the ideal candidate for the applicative approach! Let’s update the service object itself:

```
class ProcessOrder < MultiStepService
def initialize(order) = @order = order
add_step :deduct_from_user_account
add_step :prepare_shipment
add_step :update_order_status
def deduct_from_user_account
# ...
end
def prepare_shipment
# ...
end
def update_order_status
# ...
end
end
```

Now we need to add the base class:

```
def identity(value) = value # the most helpful function ever
def Right(value = method(:identity)) = Either::Right.new(value) # but we need it to make application work
def Left(error = method(:identity)) = Either::Left.new(error)
class MultiStepService
class << self
def add_step(step) = steps << step
def steps = @steps ||= []
end
def perform
ApplicationRecord.transaction do
self.class.steps
.reduce(Right()) { |result, step| result ^ send(step) }
.on_error { |error| raise ActiveRecord::Rollback.new(error) }
end
end
end
```

`#add_step`

collects a list of methods to be called. `#perform`

reduces steps using the `Right()`

as the initial value. `Right()`

holds the function that just returns a value passed to it (`identity`

), which makes the application work: `^`

will run steps until we execute them all or get the first error.

You can find the full example here.

## So the only use of applicatives is error handling?

Nope! In this post we explored a single container called `Either`

(and `Maybe`

, because it’s almost the same thing), but there are many more data structures that can implement `Functor`

and `Applicative`

interfaces! Also, the implementation of the applicative functor that follows the laws can be either used as the argument of other functions or have some extensions (like `Monad`

). These way of combining functions can lead us to more complex and interesting calculations.

## Outro

Railway programming gives us a clearer way to handle errors: we describe a golden path and errors are handled by the external code in the predictable way. It is usually implemented using `Either`

/`Maybe`

monads, but we saw how to replace them with applicative functors in cases when steps are completely independent.

There is a common mistake to think that monads and applicative functors can be used only for that. In the next post we will see how different data structures can implement the `Applicative`

interface and what interesting behavior they can provide to us.