Skip to content

Instantly share code, notes, and snippets.

@revskill10
Created February 11, 2023 12:58
Show Gist options
  • Save revskill10/bd7b8073760edba1eba1f1ca93e3a548 to your computer and use it in GitHub Desktop.
Save revskill10/bd7b8073760edba1eba1f1ca93e3a548 to your computer and use it in GitHub Desktop.
Explain Monad using Ruby

#Monad


  1. Explain Haskell typeclass using Ruby Type classes in Haskell can be thought of as similar to Ruby modules. Just like Ruby modules, type classes define a set of methods that can be implemented for different data types. However, unlike Ruby modules, type classes can be used to provide a common interface for data types that are defined in different places and by different libraries.

In Haskell, type classes are defined using the class keyword, followed by the name of the type class and the methods it defines. For example, here's how you might define a type class for types that can be compared for equality:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)

To implement a type class for a particular type, you use the instance keyword, followed by the name of the type class and the type that you want to implement it for. For example, here's how you might implement the Eq type class for the Person type:

data Person = Person { name :: String, age :: Int }

instance Eq Person where
    (==) (Person n1 a1) (Person n2 a2) = n1 == n2 && a1 == a2
    (/=) (Person n1 a1) (Person n2 a2) = n1 /= n2 || a1 /= a2

With this implementation, you can now use the == and /= operators to compare Person values for equality. This is just one example of what type classes can be used for in Haskell; they can be used to define a wide range of common interfaces for different types

Write equivalent Ruby code to implement above Eq typeclass

module Eq
  def eq(other)
    (self == other)
  end

  def neq(other)
    !(self == other)
  end
end

class Person
  attr_reader :name, :age

  include Eq

  def initialize(name, age)
    @name = name
    @age = age
  end

  def ==(other)
    name == other.name && age == other.age
  end
end

In this implementation, the Eq module defines two methods, eq and neq, which can be used to check equality and inequality between objects, respectively. The Person class includes the Eq module, and also defines its own == method, which implements the comparison logic specific to Person objects.

This way, you can use the eq and neq methods to check equality and inequality between Person objects, just like in the Haskell implementation. For example:

p1 = Person.new("John", 30)
p2 = Person.new("Jane", 29)

puts p1.eq(p2) # false
puts p1.neq(p2) # true

Implement Functor typeclass using Ruby


In Haskell, the Functor type class is used to define a common interface for types that can be "mapped" over, meaning that a function can be applied to each element of the type in some way. Here's an example of how the Functor type class is defined in Haskell:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

To implement the Functor type class in Ruby, you can define a module with the same name that implements the equivalent logic. Here's an example implementation:

module Functor
  def fmap
    raise NotImplementedError, "The fmap method must be implemented in a Functor subclass."
  end
end

class Array
  include Functor

  def fmap
    result = []
    each { |elem| result << yield(elem) }
    result
  end
end

In this implementation, the Functor module defines the fmap method, which takes a block and applies it to each element of the type. The Array class includes the Functor module, and provides its own implementation of fmap, which applies the block to each element of the array using the each method.

You can use the fmap method just like in Haskell, to apply a function to each element of an array:

array = [1, 2, 3, 4]

squared = array.fmap { |x| x**2 }

puts squared # [1, 4, 9, 16]

Go on with implementing Applicative typeclass for above Array class

The Applicative type class in Haskell extends the Functor type class and adds the ability to apply functions contained within the type to values contained within the type. Here's an example of how the Applicative type class is defined in Haskell:

class Functor f => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b


To implement the Applicative type class for the Array class in Ruby, you can add the following code to the previous implementation:

module Applicative
  def pure(value)
    [value]
  end

  def apply
    raise NotImplementedError, "The apply method must be implemented in an Applicative subclass."
  end
end

class Array
  include Functor
  include Applicative

  def apply
    result = []
    each do |elem|
      result = result.product(yield(elem)).map { |x| x.flatten }
    end
    result
  end
end

In this implementation, the Applicative module defines the pure method, which creates an array with the given value, and the apply method, which applies the function contained within the array to the values contained within the array. The Array class includes both the Functor and Applicative modules, and provides its own implementation of apply, which computes the Cartesian product of the arrays using the product method, and flattens the result using the flatten method.

You can use the pure and apply methods just like in Haskell, to apply functions contained within arrays to values contained within arrays:

add = -> (x, y) { x + y }

array1 = [1, 2, 3, 4].pure
array2 = [10, 20, 30].pure

sum = array1.apply { |x| array2.fmap { |y| add.curry.(x).(y) } }

puts sum # [[11, 21, 31], [12, 22, 32], [13, 23, 33], [14, 24, 34]]


This is just one example of how you can implement the Applicative type class for the Array class in Ruby. You could also implement it for other types, such as Hash, Set, or custom classes that you define.

How about implementing Monad for above Array ?

The Monad type class in Haskell extends the Applicative type class and adds the ability to chain together computations and pass values between them, while automatically handling the context of those computations. Here's an example of how the Monad type class is defined in Haskell:

class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

To implement the Monad type class for the Array class in Ruby, you can add the following code to the previous implementation:

module Monad
  def return(value)
    pure(value)
  end

  def bind
    raise NotImplementedError, "The bind method must be implemented in a Monad subclass."
  end
end

class Array
  include Functor
  include Applicative
  include Monad

  def bind
    result = []
    each do |elem|
      result.concat(yield(elem))
    end
    result
  end
end

In this implementation, the Monad module defines the return method, which is an alias for pure, and the bind method, which chains together computations and passes values between them. The Array class includes both the Functor, Applicative, and Monad modules, and provides its own implementation of bind, which concatenates the arrays using the concat method.

You can use the return and bind methods just like in Haskell, to chain together computations and pass values between them, while automatically handling the context of those computations:

add = -> (x, y) { x + y }

array1 = [1, 2, 3, 4].return
array2 = [10, 20, 30].return

sum = array1.bind do |x|
  array2.bind do |y|
    [add.(x, y)]
  end
end

puts sum # [11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34]


This is just one example of how you can implement the Monad type class for the Array class in Ruby. You could also implement it for other types, such as Hash, Set, or custom classes that you define.

Let's go on with state monad using Ruby

The State monad in Haskell is a type of monad that represents computations that can carry some state along with them. Here's an example of how the State monad is defined in Haskell:

newtype State s a = State { runState :: s -> (a, s) }

instance Monad (State s) where
    return a = State $ \s -> (a, s)
    m >>= k  = State $ \s -> let (a, s') = runState m s
                                 (b, s'') = runState (k a) s'
                             in (b, s'')

To implement the State monad in Ruby, you can define a State class that wraps a lambda (or Proc) function and provides methods to interact with the state:

class State
  def initialize(lambda)
    @lambda = lambda
  end

  def call(state)
    @lambda.call(state)
  end

  def self.return(value)
    State.new(-> (state) { [value, state] })
  end

  def bind
    State.new(-> (state) {
      result = @lambda.call(state)
      value = result[0]
      new_state = result[1]
      yield(value).call(new_state)
    })
  end
end


In this implementation, the State class takes a lambda function that takes a state as input and returns a tuple of a result value and a new state. The return method creates a new State monad with the given value and the current state, and the bind method chains together computations and passes values between them.

Here's an example of how you can use the State monad in Ruby to carry state through a computation:

increment = State.new(-> (state) { [nil, state + 1] })

state = 0
result = increment.bind do
  increment.bind do
    State.return(:done)
  end
end.call(state)

value = result[0]
new_state = result[1]

puts value    # :done
puts new_state # 2

In this example, the increment State monad increments the state by 1 each time it is called, and the State.return(:done) method returns the value :done along with the current state. The computation is executed by calling the call method on the final State monad, passing in an initial state of 0. The result is a tuple of the value :done and the final state of 2, indicating that the state has been incremented twice.

Let's combine Maybe monad with State monad with monad transformer using Ruby for above computation

In Haskell, you can use a monad transformer to combine multiple monads into a single, more complex monad. For example, you can use the MaybeT monad transformer to add a layer of maybe-ness to a monad, like the State monad. The resulting MaybeT (State s) a monad represents computations that can carry state and have a potential for failure, represented by Nothing.

Here's an example of how the MaybeT monad transformer is defined in Haskell:

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

instance Monad m => Monad (MaybeT m) where
    return = MaybeT . return . Just
    x >>= f = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> return Nothing
            Just y -> runMaybeT (f y)

instance MonadTrans MaybeT where
    lift = MaybeT . liftM Just

To implement a similar monad transformer in Ruby, you can define a MaybeT class that wraps an underlying monad and provides methods to interact with the maybe-ness of the computation:

class MaybeT
  def initialize(monad)
    @monad = monad
  end

  def self.return(value)
    MaybeT.new(monad.return(Just.new(value)))
  end

  def bind
    MaybeT.new(@monad.bind do |maybe_value|
      case maybe_value
      when Nothing
        monad.return(Nothing)
      when Just
        yield(maybe_value.value)
      end
    end)
  end
end

In this implementation, the MaybeT class takes an underlying monad as input and provides methods for working with the maybe-ness of the computation. The return method creates a new MaybeT monad with a Just value and the underlying monad's current state, and the bind method chains together computations and passes values between them, with the possibility of short-circuiting the computation if a Nothing value is encountered.

Here's an example of how you can use the MaybeT monad transformer in combination with the State monad in Ruby to carry state through a computation that has the potential for failure:

increment = State.new(-> (state) { [nil, state + 1] })

state = 0
result = MaybeT.new(increment.bind do
  increment.bind do
    State.return(Just.new(:done))
  end
end).call(state)

value = result[0]
new_state = result[1]

puts value.value    # :done
puts new_state # 2

In this example, the increment State monad increments the state by 1 each time it is called, and the State.return(Just.new(:done)) method returns the value :done along with the current state, wrapped in a Just object. The MaybeT.new method wraps the State monad in a layer of maybe-ness, so that the computation can fail if necessary. The result is a tuple of the Just value :done and the final state of 2, indicating that the

Let's add getState and setState methods to above computation

To add get_state and set_state methods to the State monad in Ruby, you can define additional State methods that return new State monads with modified state:

class State
  def self.get_state
    State.new(-> (state) { [state, state] })
  end

  def self.set_state(new_state)
    State.new(-> (_) { [nil, new_state] })
  end
end

Here, the get_state method returns a new State monad that returns the current state without modifying it, and the set_state method returns a new State monad that sets the state to the given value without returning any result.

With these methods in place, you can now access and modify the state within a computation:

state = 0
result = MaybeT.new(increment.bind do
  increment.bind do
    State.get_state.bind do |current_state|
      State.set_state(current_state * 2).bind do
        State.return(Just.new(:done))
      end
    end
  end
end).call(state)

value = result[0]
new_state = result[1]

puts value.value    # :done
puts new_state # 4

In this example, the get_state method retrieves the current state and the set_state method sets the state to double its current value, so the final state after the computation is 4.


Reimplement monad do in Haskell using Ruby and give example for above computation

In Ruby, you can simulate the do notation from Haskell by using method chaining and blocks. Here's an equivalent implementation of the MaybeT monad transformer with the State monad using the do notation in Ruby:

class MaybeT
  def self.do
    maybe = yield
    MaybeT.new(maybe)
  end
end

state = 0
result = MaybeT.do
  increment = State.increment
  a = increment.bind do
    increment.bind do
      State.get_state.bind do |current_state|
        State.set_state(current_state * 2).bind do
          State.return(Just.new(:done))
        end
      end
    end
  end
  a
end.call(state)

value = result[0]
new_state = result[1]

puts value.value    # :done
puts new_state # 4


In this example, the MaybeT.do block acts as a constructor for the MaybeT monad, with the result of the block being passed to the MaybeT.new method to create the final monad. This allows you to use the do notation to write the computation in a more readable and concise way, without having to manually create and bind monads.

Let's add the Except monad to above computation to raise some error

To add error handling to the computation using an Except monad, you can modify the computation to return an Except monad instead of a Just monad in case of an error:

class Except
  attr_reader :value

  def initialize(value)
    @value = value
  end
end

class State
  def increment
    State.new(-> (state) { [nil, state + 1] })
  end

  def self.return(value)
    State.new(-> (state) { [value, state] })
  end

  def bind
    result = call(@state)
    value = result[0]
    new_state = result[1]
    yield(value, new_state)
  end

  def self.get_state
    State.new(-> (state) { [state, state] })
  end

  def self.set_state(new_state)
    State.new(-> (_) { [nil, new_state] })
  end
end

class MaybeT
  def self.do
    maybe = yield
    MaybeT.new(maybe)
  end
end

state = 0
result = MaybeT.do
  increment = State.increment
  a = increment.bind do
    increment.bind do
      State.get_state.bind do |current_state|
        if current_state > 2
          State.return(Except.new("Too many increments"))
        else
          State.set_state(current_state * 2).bind do
            State.return(Just.new(:done))
          end
        end
      end
    end
  end
  a
end.call(state)

value = result[0]
new_state = result[1]

if value.is_a?(Except)
  puts value.value    # "Too many increments"
else
  puts value.value    # :done
end

puts new_state # 4

In this example, the computation checks if the current state is greater than 2 after two increments, and if it is, it returns an Except monad with the error message "Too many increments". The final result of the computation will either be a Just monad with the value :done or an Except monad with the error message, depending on the value of the state.


Can we use ExceptT monad for above computation instead ?

Yes, you can use the ExceptT monad transformer instead of the Except monad for error handling in the computation. Here's an example:

class Except
  attr_reader :value

  def initialize(value)
    @value = value
  end
end

class ExceptT
  attr_reader :run_except_t

  def initialize(run_except_t)
    @run_except_t = run_except_t
  end

  def bind
    result = @run_except_t.call
    value = result[0]
    new_state = result[1]
    if value.is_a?(Except)
      ExceptT.new(-> { [value, new_state] })
    else
      yield(value, new_state)
    end
  end

  def self.return(value)
    ExceptT.new(-> { [value, nil] })
  end
end

class State
  def increment
    State.new(-> (state) { [nil, state + 1] })
  end

  def self.return(value)
    State.new(-> (state) { [value, state] })
  end

  def bind
    result = call(@state)
    value = result[0]
    new_state = result[1]
    yield(value, new_state)
  end

  def self.get_state
    State.new(-> (state) { [state, state] })
  end

  def self.set_state(new_state)
    State.new(-> (_) { [nil, new_state] })
  end
end

class MaybeT
  def self.do
    maybe = yield
    MaybeT.new(maybe)
  end
end

state = 0
result = MaybeT.do
  increment = State.increment
  a = increment.bind do
    increment.bind do
      State.get_state.bind do |current_state|
        if current_state > 2
          ExceptT.return(Except.new("Too many increments"))
        else
          State.set_state(current_state * 2).bind do
            State.return(Just.new(:done))
          end
        end
      end
    end
  end
  a
end.call(state)

value = result[0]
new_state = result[1]

if value.is_a?(Except)
  puts value.value    # "Too many increments"
else
  puts value.value    # :done
end

puts new_state # 4

In this example, the ExceptT monad transformer is used to wrap the Except monad, allowing for easier composition with other monads like MaybeT and State. The bind method for the ExceptT monad checks if the value is an Except monad, and if it is, it returns the ExceptT monad with the error message, otherwise it yields to the given block to continue the computation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment