Full Code of txus/kleisli for AI

master 65d5eac2d70e cached
23 files
21.9 KB
7.3k tokens
129 symbols
1 requests
Download .txt
Repository: txus/kleisli
Branch: master
Commit: 65d5eac2d70e
Files: 23
Total size: 21.9 KB

Directory structure:
gitextract_4nn5_ypf/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── kleisli.gemspec
├── lib/
│   ├── kleisli/
│   │   ├── composition.rb
│   │   ├── either.rb
│   │   ├── functor.rb
│   │   ├── future.rb
│   │   ├── maybe.rb
│   │   ├── monad.rb
│   │   ├── try.rb
│   │   └── version.rb
│   └── kleisli.rb
└── test/
    ├── kleisli/
    │   ├── composition_test.rb
    │   ├── either_test.rb
    │   ├── future_test.rb
    │   ├── maybe_test.rb
    │   └── try_test.rb
    └── test_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
  pull_request:

jobs:
  test:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        ruby-version:
          - 3.2
          - 3.1
          - "3.0"

    steps:
      - uses: actions/checkout@v3
      - name: Set up Ruby ${{ matrix.ruby-version }}
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}
          bundler-cache: true # runs 'bundle install' and caches installed gems automatically
      - name: Run tests
        run: bundle exec rake


================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
*.bundle
*.so
*.o
*.a
mkmf.log
*.gem


================================================
FILE: .travis.yml
================================================
language: ruby

dist: trusty

matrix:
  include:
    - rvm: 2.1
    - rvm: 2.2
    - rvm: 2.3.3
    - rvm: 2.4.0
    - rvm: ruby-head
    - rvm: jruby-head
      env: "JRUBY_OPTS=--debug"
      before_install:
        - gem install bundler -v'1.13.7'
    - rvm: jruby-9.1.7.0
      env: "JRUBY_OPTS=--debug"

script:
  - rake


================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

# Specify your gem's dependencies in kleisli.gemspec
gemspec


================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2014 Josep M. Bach, Ryan Levick

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# Kleisli [![Build Status](https://secure.travis-ci.org/txus/kleisli.svg)](http://travis-ci.org/txus/kleisli)

An idiomatic, clean implementation of a few common useful monads in Ruby,
written by [Ryan Levick][rylev] and me.

It aims to be idiomatic Ruby to use in Enter-Prise production apps, not a proof
of concept.

In your Gemfile:

```ruby
gem 'kleisli'
```

We would like to thank Curry and Howard for their correspondence.

## Notation

For all its monads, Kleisli implements `return` (we call it `lift` instead, as
`return` is a reserved keyword in Ruby) with convenience global methods (see
which for each monad below).

Kleisli uses a clever Ruby syntax trick to implement the `bind` operator, which
looks like this: `>->` when used with a block. We will probably burn in hell
for this. You can also use `>` or `>>` if you're going to pass in a proc or
 lambda object.

`Maybe` and `Either` are applicative functors with the apply operator `*`. Read
further to see how it works.

### Function composition

You can use Haskell-like function composition with F and the familiar `.`. This
is such a perversion of Ruby syntax that Matz would probably condemn this:

Think of `F` as the identity function. Although it's just a hack to make it
work in Ruby.

```ruby
# Reminder that (f . g) x= f(g(x))
f = F . first . last
f.call [[1,2], [3,4]]
# => 3

f = F . capitalize . reverse
f.call "hello"
# => "Olleh"
```

Functions and methods are interchangeable:

```ruby
foo = lambda { |s| s.reverse }

f = F . capitalize . fn(&foo)
f.call "hello"
# => "Olleh"
```

All functions and methods are partially applicable:

```ruby

# Partially applied method:
f = F . split(":") . strip
f.call "  localhost:9092     "
# => ["localhost", "9092"]

# Partially applied lambda:
my_split = lambda { |str, *args| str.split(*args) }
f = F . fn(":", &my_split) . strip
f.call "  localhost:9092     "
# => ["localhost", "9092"]
```

Finally, for convenience, `F` is the identity function:

```ruby
F.call(1) # => 1
```

## Maybe monad

The Maybe monad is useful to express a pipeline of computations that might
return nil at any point. `user.address.street` anyone?

### `>->` (bind)

```ruby
require "kleisli"

maybe_user = Maybe(user) >-> user {
  Maybe(user.address) } >-> address {
    Maybe(address.street) }

# If user exists
# => Some("Monad Street")
# If user is nil
# => None()

# You can also use Some and None as type constructors yourself.
x = Some(10)
y = None()

```

As usual (with Maybe and Either), using point-free style is much cleaner:

```ruby
Maybe(user) >> F . fn(&Maybe) . address >> F . fn(&Maybe) . street
```

### `fmap`

```ruby
require "kleisli"

# If we know that a user always has an address with a street
Maybe(user).fmap(&:address).fmap(&:street)

# If the user exists
# => Some("Monad Street")
# If the user is nil
# => None()
```

### `*` (applicative functor's apply)

```ruby
require "kleisli"

add = -> x, y { x + y }
Some(add) * Some(10) * Some(2)
# => Some(12)
Some(add) * None() * Some(2)
# => None
```

## Try

The Try monad is useful to express a pipeline of computations that might throw
an exception at any point.

### `>->` (bind)

```ruby
require "kleisli"

json_string = get_json_from_somewhere

result = Try { JSON.parse(json_string) } >-> json {
  Try { json["dividend"].to_i / json["divisor"].to_i }
}

# If no exception was thrown:

result       # => #<Try::Success @value=123>
result.value # => 123

# If there was a ZeroDivisionError exception for example:

result           # => #<Try::Failure @exception=#<ZeroDivisionError ...>>
result.exception # => #<ZeroDivisionError ...>
```

### `fmap`

```ruby
require "kleisli"

Try { JSON.parse(json_string) }.fmap(&:symbolize_keys).value

# If everything went well:
# => { :my => "json", :with => "symbolized keys" }
# If an exception was thrown:
# => nil
```

### `to_maybe`

Sometimes it's useful to interleave both `Try` and `Maybe`. To convert a `Try`
into a `Maybe` you can use `to_maybe`:

```ruby
require "kleisli"

Try { JSON.parse(json_string) }.fmap(&:symbolize_keys).to_maybe

# If everything went well:
# => Some({ :my => "json", :with => "symbolized keys" })
# If an exception was thrown:
# => None()
```

### `to_either`

Sometimes it's useful to interleave both `Try` and `Either`. To convert a `Try`
into a `Either` you can use `to_either`:

```ruby
require "kleisli"

Try { JSON.parse(json_string) }.fmap(&:symbolize_keys).to_either

# If everything went well:
# => Right({ :my => "json", :with => "symbolized keys" })
# If an exception was thrown:
# => Left(#<JSON::ParserError: 757: unexpected token at 'json'>)
```

## Either

The Either monad is useful to express a pipeline of computations that might return an error object with some information.

It has two type constructors: `Right` and `Left`. As a useful mnemonic, `Right` is for when everything went "right" and `Left` is used for errors.

Think of it as exceptions without messing with the call stack.

### `>->` (bind)

```ruby
require "kleisli"

result = Right(3) >-> value {
  if value > 1
    Right(value + 3)
  else
    Left("value was less or equal than 1")
  end
} >-> value {
  if value % 2 == 0
    Right(value * 2)
  else
    Left("value was not even")
  end
}

# If everything went well
result # => Right(12)
result.value # => 12

# If it failed in the first block
result # => Left("value was less or equal than 1")
result.value # => "value was less or equal than 1"

# If it failed in the second block
result # => Left("value was not even")
result.value # => "value was not even"

# Point-free style bind!
result = Right(3) >> F . fn(&Right) . *(2)
result # => Right(6)
result.value # => 6
```

### `fmap`

```ruby
require "kleisli"

result = if foo > bar
  Right(10)
else
  Left("wrong")
end.fmap { |x| x * 2 }

# If everything went well
result # => Right(20)
# If it didn't
result # => Left("wrong")
```

### `*` (applicative functor's apply)

```ruby
require "kleisli"

add = -> x, y { x + y }
Right(add) * Right(10) * Right(2)
# => Right(12)
Right(add) * Left("error") * Right(2)
# => Left("error")
```

### `or`

`or` does pretty much what would you expect:

```ruby
require 'kleisli'

Right(10).or(Right(999)) # => Right(10)
Left("error").or(Left("new error")) # => Left("new error")
Left("error").or { |err| Left("new #{err}") } # => Left("new error")
```

### `to_maybe`

Sometimes it's useful to turn an `Either` into a `Maybe`. You can use
`to_maybe` for that:

```ruby
require "kleisli"

result = if foo > bar
  Right(10)
else
  Left("wrong")
end.to_maybe

# If everything went well:
result # => Some(10)
# If it didn't
result # => None()
```

## Future

The Future monad models a pipeline of computations that will happen in the future, as soon as the value needed for each step is available. It is useful to model, for example, a sequential chain of HTTP calls.

There's a catch unfortunately -- values passed to the functions are wrapped in
lambdas, so you need to call `.call` on them. See the examples below.

### `>->` (bind)

```ruby
require "kleisli"

f = Future("myendpoint.com") >-> url {
  Future { HTTP.get(url.call) }
} >-> response {
  Future {
    other_url = JSON.parse(response.call.body)[:other_url]
    HTTP.get(other_url)
  }
} >-> other_response {
  Future { JSON.parse(other_response.call.body) }
}

# Do some other stuff...

f.await # => block until the whole pipeline is realized
# => { "my" => "response body" }
```

### `fmap`

```ruby
require "kleisli"

Future { expensive_operation }.fmap { |x| x * 2 }.await
# => result of expensive_operation * 2
```

## Who's this

This was made by [Josep M. Bach (Txus)](http://blog.txus.io) and [Ryan
Levick][rylev] under the MIT license. We are [@txustice][twitter] and
[@itchyankles][itchyankles] on twitter (where you should probably follow us!).

[twitter]: https://twitter.com/txustice
[itchyankles]: https://twitter.com/itchyankles
[rylev]: https://github.com/rylev


================================================
FILE: Rakefile
================================================
require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new do |t|
  t.libs << "test"
  t.test_files = FileList['test/**/*_test.rb']
  t.verbose = true
end

task :default => :test


================================================
FILE: kleisli.gemspec
================================================
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'kleisli/version'

Gem::Specification.new do |spec|
  spec.name          = "kleisli"
  spec.version       = Kleisli::VERSION
  spec.authors       = ["Josep M. Bach", "Ryan Levick"]
  spec.email         = ["josep.m.bach@gmail.com", "ryan.levick@gmail.com"]
  spec.summary       = %q{Usable, idiomatic common monads in Ruby}
  spec.description   = %q{Usable, idiomatic common monads in Ruby}
  spec.homepage      = "https://github.com/txus/kleisli"
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("\x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.6"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "minitest", "~> 5.5"
end


================================================
FILE: lib/kleisli/composition.rb
================================================
module Kleisli
  class ComposedFn < BasicObject
    def self.comp(f, g)
      lambda { |*args| f[g[*args]] }
    end

    def initialize(fns=[])
      @fns = fns
    end

    def fn(*args, &block)
      f = -> arguments, receiver {
        block.call(receiver, *arguments)
      }.curry[args]
      ComposedFn.new(@fns + [f])
    end

    def method_missing(meth, *args, &block)
      f = -> arguments, receiver {
        receiver.send(meth, *arguments, &block)
      }.curry[args]
      ComposedFn.new(@fns + [f])
    end

    def call(*args)
      if @fns.any?
        @fns.reduce { |f, g| ComposedFn.comp(f, g) }.call(*args)
      else
        args.first
      end
    end

    def to_ary
      @fns.to_ary
    end
  end
end

F = Kleisli::ComposedFn.new


================================================
FILE: lib/kleisli/either.rb
================================================
require 'kleisli/monad'
require 'kleisli/maybe'

module Kleisli
  class Either < Monad
    attr_reader :right, :left

    def ==(other)
      right == other.right && left == other.left
    end

    def *(other)
      self >-> f {
        other >-> val {
          Right(f.arity > 1 ? f.curry.call(val) : f.call(val))
        }
      }
    end

    class Right < Either
      alias value right

      def initialize(right)
        @right = right
      end

      def >(f)
        f.call(@right)
      end

      def fmap(&f)
        Right.new(f.call(@right))
      end

      def to_maybe
        Maybe::Some.new(@right)
      end

      def or(other=nil, &other_blk)
        self
      end

      def to_s
        "Right(#{@right.inspect})"
      end
      alias inspect to_s
    end

    class Left < Either
      alias value left

      def initialize(left)
        @left = left
      end

      def >(f)
        self
      end

      def fmap(&f)
        self
      end

      def to_maybe
        Maybe::None.new
      end

      def or(other=self, &other_blk)
        if other_blk
          other_blk.call(@left)
        else
          other
        end
      end

      def to_s
        "Left(#{@left.inspect})"
      end
      alias inspect to_s
    end
  end
end

Right = Kleisli::Either::Right.method(:new)
Left = Kleisli::Either::Left.method(:new)

def Right(v)
  Kleisli::Either::Right.new(v)
end

def Left(v)
  Kleisli::Either::Left.new(v)
end


================================================
FILE: lib/kleisli/functor.rb
================================================
module Kleisli
  class Functor
    def fmap(&f)
      raise NotImplementedError, "this functor doesn't implement fmap"
    end
  end
end


================================================
FILE: lib/kleisli/future.rb
================================================
require 'kleisli/monad'

module Kleisli
  class Future < Monad
    def self.lift(v=nil, &block)
      if block
        new(Thread.new(&block))
      else
        new(Thread.new { v })
      end
    end

    def initialize(t)
      @t = t
    end

    def >(f)
      f.call(-> { await })
    end

    def fmap(&f)
      Future.lift(f.call(-> { await }))
    end

    def await
      @t.join.value
    end
  end
end

def Future(v=nil, &block)
  Kleisli::Future.lift(v, &block)
end


================================================
FILE: lib/kleisli/maybe.rb
================================================
require 'kleisli/monad'

module Kleisli
  class Maybe < Monad
    attr_reader :value

    def self.lift(value)
      if value.nil?
        None.new
      else
        Some.new(value)
      end
    end

    def ==(other)
      value == other.value
    end

    def *(other)
      self >-> f {
        f = f.to_proc
        other >-> val {
          Maybe.lift(f.arity > 1 ? f.curry.call(val) : f.call(val))
        }
      }
    end

    class None < Maybe
      def fmap(&f)
        self
      end

      def >(block)
        self
      end

      def or(other=self, &other_blk)
        if other_blk
          other_blk.call
        else
          other
        end
      end

      def to_s
        "None"
      end
      alias inspect to_s
    end

    class Some < Maybe
      def initialize(value)
        @value = value
      end

      def fmap(&f)
        Maybe.lift(f.call(@value))
      end

      def >(block)
        block.call(@value)
      end

      def or(other=nil, &other_blk)
        self
      end

      def to_s
        "Some(#{@value.inspect})"
      end
      alias inspect to_s
    end
  end
end

Maybe = Kleisli::Maybe.method(:lift)

def Maybe(v)
  Maybe.(v)
end

def None()
  Maybe(nil)
end

def Some(v)
  Maybe(v)
end


================================================
FILE: lib/kleisli/monad.rb
================================================
require "kleisli/functor"

module Kleisli
  class Monad < Functor
    def >(block)
      raise NotImplementedError, "this monad doesn't implement >->"
    end

    def >>(block)
      self > block
    end
  end
end


================================================
FILE: lib/kleisli/try.rb
================================================
require 'kleisli/monad'
require 'kleisli/maybe'

module Kleisli
  class Try < Monad
    attr_reader :exception, :value

    def self.lift(f)
      Success.new(f.call)
    rescue => e
      Failure.new(e)
    end

    class Success < Try
      def initialize(value)
        @value = value
      end

      def >(f)
        f.call(@value)
      rescue => e
        Failure.new(e)
      end

      def fmap(&f)
        Try { f.call(@value) }
      end

      def to_maybe
        Maybe::Some.new(@value)
      end

      def to_either
        Either::Right.new(@value)
      end
    end

    class Failure < Try
      def initialize(exception)
        @exception = exception
      end

      def >(f)
        self
      end

      def fmap(&f)
        self
      end

      def to_maybe
        Maybe::None.new
      end

      def to_either
        Either::Left.new(@exception)
      end
    end
  end
end

def Try(&f)
  Kleisli::Try.lift(f)
end


================================================
FILE: lib/kleisli/version.rb
================================================
module Kleisli
  VERSION = "0.2.7"
end


================================================
FILE: lib/kleisli.rb
================================================
require "kleisli/version"
require "kleisli/maybe"
require "kleisli/try"
require "kleisli/future"
require "kleisli/either"
require "kleisli/composition"

module Kleisli
end


================================================
FILE: test/kleisli/composition_test.rb
================================================
require 'test_helper'

class CompositionTest < Minitest::Test
  def test_one_method
    f = F . first
    result = f.call([1])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 1, result
  end

  def test_two_methods
    f = F . first . last
    result = f.call([1, [2,3]])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 2, result
  end

  def test_one_function
    my_first = lambda { |x| x.first }

    f = F . fn(&my_first)
    result = f.call([1])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 1, result
  end

  def test_two_functions
    my_first = lambda { |x| x.first }
    my_last = lambda { |x| x.last }

    f = F . fn(&my_first) . fn(&my_last)
    result = f.call([1, [2,3]])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 2, result
  end

  def test_one_function_one_block
    my_last = lambda { |x| x.last }

    f = F . fn { |x| x.first } . fn(&my_last)
    result = f.call([1, [2,3]])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 2, result
  end

  def test_one_function_one_method
    my_last = lambda { |x| x.last }

    f = F . first . fn(&my_last)
    result = f.call([1, [2,3]])
    assert Fixnum === result, "#{result} is not a number"
    assert_equal 2, result
  end

  def test_undefined_method
    f = F . foo
    assert_raises(NoMethodError) { f.call(1) }
  end

  def test_identity
    assert_equal 1, F.call(1)
  end

  def test_partially_applied_method
    f = F . split(":")
    result = f.call("localhost:9092")
    assert Array === result, "#{result} is not an array"
    assert_equal ["localhost", "9092"], result
  end

  def test_partially_applied_fn
    split = lambda { |x, *args| x.split(*args) }
    f = F . fn(":", &split)
    result = f.call("localhost:9092")
    assert Array === result, "#{result} is not an array"
    assert_equal ["localhost", "9092"], result
  end
end


================================================
FILE: test/kleisli/either_test.rb
================================================
require 'test_helper'

class EitherTest < Minitest::Test
  def test_lift_right
    assert_equal 3, Right(3).value
  end

  def test_lift_left
    assert_equal "error", Left("error").value
  end

  def test_bind_right
    v = Right(1) >-> x {
      if x == 1
        Right(x + 90)
      else
        Left("FAIL")
      end
    }
    assert_equal Right(91), v
  end

  def test_bind_left
    v = Left("error") >-> x {
      Right(x * 20)
    }
    assert_equal Left("error"), v
  end

  def test_fmap_right
    assert_equal Right(2), Right(1).fmap { |x| x * 2 }
  end

  def test_fmap_left
    assert_equal Left("error"), Left("error").fmap { |x| x * 2 }
  end

  def test_to_maybe_right
    assert_equal Some(2), Right(1).fmap { |x| x * 2 }.to_maybe
  end

  def test_to_maybe_left
    assert_equal None(), Left("error").fmap { |x| x * 2 }.to_maybe
  end

  def test_pointfree
    assert_equal Right(10), Right(5) >> F . fn(&Right) . *(2)
  end

  def test_applicative_functor_right_arity_1
    assert_equal Right(20), Right(-> x { x * 2 }) * Right(10)
  end

  def test_applicative_functor_right_arity_2
    assert_equal Right(20), Right(-> x, y { x * y }) * Right(10) * Right(2)
  end

  def test_applicative_functor_left
    assert_equal Left("error"), Right(-> x, y { x * y }) * Left("error") * Right(2)
  end
end


================================================
FILE: test/kleisli/future_test.rb
================================================
require 'test_helper'

class FutureTest < Minitest::Test
  def test_immediate_value
    assert_equal 30, Future(30).await
  end

  def test_simple_future_executes_in_parallel
    str = ""
    Future { sleep 0.1; str << "bar" }.tap {
      str << "foo"
    }.await
    assert_equal "foobar", str
  end

  def test_bind
    f = Future(30) >-> n {
      Future { n.call * 2 }
    } >-> n {
      Future { n.call * 2 } >-> m {
        Future(m.call + 2)
      }
    }
    assert_equal 122, f.await
  end

  def test_fmap
    f = Future(30).fmap { |x| x.call * 2 }
    assert_equal 60, f.await
  end
end


================================================
FILE: test/kleisli/maybe_test.rb
================================================
require 'test_helper'

class MaybeTest < Minitest::Test
  def test_unwrapping_some
    assert_equal 3, Some(3).value
  end

  def test_unwrapping_none
    assert_equal nil, None().value
  end

  def test_bind_none
    assert_equal None(), None() >> F . fn(&Maybe) . *(2)
  end

  def test_bind_some
    assert_equal Some(6), Some(3) >> F . fn(&Maybe) . *(2)
  end

  def test_fmap_none
    assert_equal None(), None().fmap { |x| x * 2 }
  end

  def test_fmap_some
    assert_equal Some(6), Some(3).fmap { |x| x * 2 }
  end

  def test_applicative_functor_some_arity_1
    assert_equal Some(20), Maybe(-> x { x * 2 }) * Maybe(10)
  end

  def test_applicative_functor_some_arity_2
    assert_equal Some(20), Maybe(-> x, y { x * y }) * Maybe(10) * Maybe(2)
  end

  def test_applicative_functor_none
    assert_equal None(), Maybe(-> x, y { x * y }) * None() * Maybe(2)
  end
end


================================================
FILE: test/kleisli/try_test.rb
================================================
require 'test_helper'

class TryTest < Minitest::Test
  def test_success
    assert_equal 2, Try { 10 / 5 }.value
  end

  def test_failure
    assert_kind_of ZeroDivisionError, Try { 10 / 0 }.exception
  end

  def test_to_maybe_success
    assert_equal Some(2), Try { 10 / 5 }.to_maybe
  end

  def test_to_maybe_failure
    assert_equal None(), Try { 10 / 0 }.to_maybe
  end

  def test_to_either_success
    assert_equal Right(2), Try { 10 / 5 }.to_either
  end

  def test_to_either_failure
    assert_kind_of ZeroDivisionError, Try { 10 / 0 }.to_either.left
  end

  def test_fmap_success
    assert_equal 4, Try { 10 / 5 }.fmap { |x| x * 2 }.value
  end

  def test_fmap_failure
    assert_kind_of ZeroDivisionError, Try { 10 / 0 }.fmap { |x| x * 2 }.exception
  end

  def test_bind
    try = Try { 20 / 10 } >-> number { Try { 10 / number } }
    assert_equal 5, try.value
  end
end


================================================
FILE: test/test_helper.rb
================================================
require 'kleisli'
require 'minitest'
require 'minitest/autorun'
Download .txt
gitextract_4nn5_ypf/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── kleisli.gemspec
├── lib/
│   ├── kleisli/
│   │   ├── composition.rb
│   │   ├── either.rb
│   │   ├── functor.rb
│   │   ├── future.rb
│   │   ├── maybe.rb
│   │   ├── monad.rb
│   │   ├── try.rb
│   │   └── version.rb
│   └── kleisli.rb
└── test/
    ├── kleisli/
    │   ├── composition_test.rb
    │   ├── either_test.rb
    │   ├── future_test.rb
    │   ├── maybe_test.rb
    │   └── try_test.rb
    └── test_helper.rb
Download .txt
SYMBOL INDEX (129 symbols across 14 files)

FILE: lib/kleisli.rb
  type Kleisli (line 8) | module Kleisli

FILE: lib/kleisli/composition.rb
  type Kleisli (line 1) | module Kleisli
    class ComposedFn (line 2) | class ComposedFn < BasicObject
      method comp (line 3) | def self.comp(f, g)
      method initialize (line 7) | def initialize(fns=[])
      method fn (line 11) | def fn(*args, &block)
      method method_missing (line 18) | def method_missing(meth, *args, &block)
      method call (line 25) | def call(*args)
      method to_ary (line 33) | def to_ary

FILE: lib/kleisli/either.rb
  type Kleisli (line 4) | module Kleisli
    class Either (line 5) | class Either < Monad
      method == (line 8) | def ==(other)
      method * (line 12) | def *(other)
      class Right (line 20) | class Right < Either
        method initialize (line 23) | def initialize(right)
        method > (line 27) | def >(f)
        method fmap (line 31) | def fmap(&f)
        method to_maybe (line 35) | def to_maybe
        method or (line 39) | def or(other=nil, &other_blk)
        method to_s (line 43) | def to_s
      class Left (line 49) | class Left < Either
        method initialize (line 52) | def initialize(left)
        method > (line 56) | def >(f)
        method fmap (line 60) | def fmap(&f)
        method to_maybe (line 64) | def to_maybe
        method or (line 68) | def or(other=self, &other_blk)
        method to_s (line 76) | def to_s
  function Right (line 87) | def Right(v)
  function Left (line 91) | def Left(v)

FILE: lib/kleisli/functor.rb
  type Kleisli (line 1) | module Kleisli
    class Functor (line 2) | class Functor
      method fmap (line 3) | def fmap(&f)

FILE: lib/kleisli/future.rb
  type Kleisli (line 3) | module Kleisli
    class Future (line 4) | class Future < Monad
      method lift (line 5) | def self.lift(v=nil, &block)
      method initialize (line 13) | def initialize(t)
      method > (line 17) | def >(f)
      method fmap (line 21) | def fmap(&f)
      method await (line 25) | def await
  function Future (line 31) | def Future(v=nil, &block)

FILE: lib/kleisli/maybe.rb
  type Kleisli (line 3) | module Kleisli
    class Maybe (line 4) | class Maybe < Monad
      method lift (line 7) | def self.lift(value)
      method == (line 15) | def ==(other)
      method * (line 19) | def *(other)
      class None (line 28) | class None < Maybe
        method fmap (line 29) | def fmap(&f)
        method > (line 33) | def >(block)
        method or (line 37) | def or(other=self, &other_blk)
        method to_s (line 45) | def to_s
      class Some (line 51) | class Some < Maybe
        method initialize (line 52) | def initialize(value)
        method fmap (line 56) | def fmap(&f)
        method > (line 60) | def >(block)
        method or (line 64) | def or(other=nil, &other_blk)
        method to_s (line 68) | def to_s
  function Maybe (line 78) | def Maybe(v)
  function None (line 82) | def None()
  function Some (line 86) | def Some(v)

FILE: lib/kleisli/monad.rb
  type Kleisli (line 3) | module Kleisli
    class Monad (line 4) | class Monad < Functor
      method > (line 5) | def >(block)
      method >> (line 9) | def >>(block)

FILE: lib/kleisli/try.rb
  type Kleisli (line 4) | module Kleisli
    class Try (line 5) | class Try < Monad
      method lift (line 8) | def self.lift(f)
      class Success (line 14) | class Success < Try
        method initialize (line 15) | def initialize(value)
        method > (line 19) | def >(f)
        method fmap (line 25) | def fmap(&f)
        method to_maybe (line 29) | def to_maybe
        method to_either (line 33) | def to_either
      class Failure (line 38) | class Failure < Try
        method initialize (line 39) | def initialize(exception)
        method > (line 43) | def >(f)
        method fmap (line 47) | def fmap(&f)
        method to_maybe (line 51) | def to_maybe
        method to_either (line 55) | def to_either
  function Try (line 62) | def Try(&f)

FILE: lib/kleisli/version.rb
  type Kleisli (line 1) | module Kleisli

FILE: test/kleisli/composition_test.rb
  class CompositionTest (line 3) | class CompositionTest < Minitest::Test
    method test_one_method (line 4) | def test_one_method
    method test_two_methods (line 11) | def test_two_methods
    method test_one_function (line 18) | def test_one_function
    method test_two_functions (line 27) | def test_two_functions
    method test_one_function_one_block (line 37) | def test_one_function_one_block
    method test_one_function_one_method (line 46) | def test_one_function_one_method
    method test_undefined_method (line 55) | def test_undefined_method
    method test_identity (line 60) | def test_identity
    method test_partially_applied_method (line 64) | def test_partially_applied_method
    method test_partially_applied_fn (line 71) | def test_partially_applied_fn

FILE: test/kleisli/either_test.rb
  class EitherTest (line 3) | class EitherTest < Minitest::Test
    method test_lift_right (line 4) | def test_lift_right
    method test_lift_left (line 8) | def test_lift_left
    method test_bind_right (line 12) | def test_bind_right
    method test_bind_left (line 23) | def test_bind_left
    method test_fmap_right (line 30) | def test_fmap_right
    method test_fmap_left (line 34) | def test_fmap_left
    method test_to_maybe_right (line 38) | def test_to_maybe_right
    method test_to_maybe_left (line 42) | def test_to_maybe_left
    method test_pointfree (line 46) | def test_pointfree
    method test_applicative_functor_right_arity_1 (line 50) | def test_applicative_functor_right_arity_1
    method test_applicative_functor_right_arity_2 (line 54) | def test_applicative_functor_right_arity_2
    method test_applicative_functor_left (line 58) | def test_applicative_functor_left

FILE: test/kleisli/future_test.rb
  class FutureTest (line 3) | class FutureTest < Minitest::Test
    method test_immediate_value (line 4) | def test_immediate_value
    method test_simple_future_executes_in_parallel (line 8) | def test_simple_future_executes_in_parallel
    method test_bind (line 16) | def test_bind
    method test_fmap (line 27) | def test_fmap

FILE: test/kleisli/maybe_test.rb
  class MaybeTest (line 3) | class MaybeTest < Minitest::Test
    method test_unwrapping_some (line 4) | def test_unwrapping_some
    method test_unwrapping_none (line 8) | def test_unwrapping_none
    method test_bind_none (line 12) | def test_bind_none
    method test_bind_some (line 16) | def test_bind_some
    method test_fmap_none (line 20) | def test_fmap_none
    method test_fmap_some (line 24) | def test_fmap_some
    method test_applicative_functor_some_arity_1 (line 28) | def test_applicative_functor_some_arity_1
    method test_applicative_functor_some_arity_2 (line 32) | def test_applicative_functor_some_arity_2
    method test_applicative_functor_none (line 36) | def test_applicative_functor_none

FILE: test/kleisli/try_test.rb
  class TryTest (line 3) | class TryTest < Minitest::Test
    method test_success (line 4) | def test_success
    method test_failure (line 8) | def test_failure
    method test_to_maybe_success (line 12) | def test_to_maybe_success
    method test_to_maybe_failure (line 16) | def test_to_maybe_failure
    method test_to_either_success (line 20) | def test_to_either_success
    method test_to_either_failure (line 24) | def test_to_either_failure
    method test_fmap_success (line 28) | def test_fmap_success
    method test_fmap_failure (line 32) | def test_fmap_failure
    method test_bind (line 36) | def test_bind
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (25K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 527,
    "preview": "name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        ru"
  },
  {
    "path": ".gitignore",
    "chars": 124,
    "preview": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n*.bundle\n*.so\n*.o\n*.a\nmkmf.log\n*."
  },
  {
    "path": ".travis.yml",
    "chars": 326,
    "preview": "language: ruby\n\ndist: trusty\n\nmatrix:\n  include:\n    - rvm: 2.1\n    - rvm: 2.2\n    - rvm: 2.3.3\n    - rvm: 2.4.0\n    - r"
  },
  {
    "path": "Gemfile",
    "chars": 92,
    "preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in kleisli.gemspec\ngemspec\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1083,
    "preview": "Copyright (c) 2014 Josep M. Bach, Ryan Levick\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "README.md",
    "chars": 7927,
    "preview": "# Kleisli [![Build Status](https://secure.travis-ci.org/txus/kleisli.svg)](http://travis-ci.org/txus/kleisli)\n\nAn idioma"
  },
  {
    "path": "Rakefile",
    "chars": 192,
    "preview": "require \"bundler/gem_tasks\"\nrequire \"rake/testtask\"\n\nRake::TestTask.new do |t|\n  t.libs << \"test\"\n  t.test_files = FileL"
  },
  {
    "path": "kleisli.gemspec",
    "chars": 993,
    "preview": "# coding: utf-8\nlib = File.expand_path('../lib', __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequi"
  },
  {
    "path": "lib/kleisli/composition.rb",
    "chars": 757,
    "preview": "module Kleisli\n  class ComposedFn < BasicObject\n    def self.comp(f, g)\n      lambda { |*args| f[g[*args]] }\n    end\n\n  "
  },
  {
    "path": "lib/kleisli/either.rb",
    "chars": 1456,
    "preview": "require 'kleisli/monad'\nrequire 'kleisli/maybe'\n\nmodule Kleisli\n  class Either < Monad\n    attr_reader :right, :left\n\n  "
  },
  {
    "path": "lib/kleisli/functor.rb",
    "chars": 137,
    "preview": "module Kleisli\n  class Functor\n    def fmap(&f)\n      raise NotImplementedError, \"this functor doesn't implement fmap\"\n "
  },
  {
    "path": "lib/kleisli/future.rb",
    "chars": 479,
    "preview": "require 'kleisli/monad'\n\nmodule Kleisli\n  class Future < Monad\n    def self.lift(v=nil, &block)\n      if block\n        n"
  },
  {
    "path": "lib/kleisli/maybe.rb",
    "chars": 1245,
    "preview": "require 'kleisli/monad'\n\nmodule Kleisli\n  class Maybe < Monad\n    attr_reader :value\n\n    def self.lift(value)\n      if "
  },
  {
    "path": "lib/kleisli/monad.rb",
    "chars": 215,
    "preview": "require \"kleisli/functor\"\n\nmodule Kleisli\n  class Monad < Functor\n    def >(block)\n      raise NotImplementedError, \"thi"
  },
  {
    "path": "lib/kleisli/try.rb",
    "chars": 944,
    "preview": "require 'kleisli/monad'\nrequire 'kleisli/maybe'\n\nmodule Kleisli\n  class Try < Monad\n    attr_reader :exception, :value\n\n"
  },
  {
    "path": "lib/kleisli/version.rb",
    "chars": 39,
    "preview": "module Kleisli\n  VERSION = \"0.2.7\"\nend\n"
  },
  {
    "path": "lib/kleisli.rb",
    "chars": 172,
    "preview": "require \"kleisli/version\"\nrequire \"kleisli/maybe\"\nrequire \"kleisli/try\"\nrequire \"kleisli/future\"\nrequire \"kleisli/either"
  },
  {
    "path": "test/kleisli/composition_test.rb",
    "chars": 1956,
    "preview": "require 'test_helper'\n\nclass CompositionTest < Minitest::Test\n  def test_one_method\n    f = F . first\n    result = f.cal"
  },
  {
    "path": "test/kleisli/either_test.rb",
    "chars": 1317,
    "preview": "require 'test_helper'\n\nclass EitherTest < Minitest::Test\n  def test_lift_right\n    assert_equal 3, Right(3).value\n  end\n"
  },
  {
    "path": "test/kleisli/future_test.rb",
    "chars": 599,
    "preview": "require 'test_helper'\n\nclass FutureTest < Minitest::Test\n  def test_immediate_value\n    assert_equal 30, Future(30).awai"
  },
  {
    "path": "test/kleisli/maybe_test.rb",
    "chars": 879,
    "preview": "require 'test_helper'\n\nclass MaybeTest < Minitest::Test\n  def test_unwrapping_some\n    assert_equal 3, Some(3).value\n  e"
  },
  {
    "path": "test/kleisli/try_test.rb",
    "chars": 892,
    "preview": "require 'test_helper'\n\nclass TryTest < Minitest::Test\n  def test_success\n    assert_equal 2, Try { 10 / 5 }.value\n  end\n"
  },
  {
    "path": "test/test_helper.rb",
    "chars": 64,
    "preview": "require 'kleisli'\nrequire 'minitest'\nrequire 'minitest/autorun'\n"
  }
]

About this extraction

This page contains the full source code of the txus/kleisli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (21.9 KB), approximately 7.3k tokens, and a symbol index with 129 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!