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 [](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'
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
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 [](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.