├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lambda_driver.gemspec ├── lib ├── lambda_driver.rb └── lambda_driver │ ├── callable.rb │ ├── composable.rb │ ├── context.rb │ ├── core_ext.rb │ ├── core_ext │ ├── class.rb │ ├── method.rb │ ├── object.rb │ ├── proc.rb │ ├── symbol.rb │ └── unbound_method.rb │ ├── currring.rb │ ├── currying.rb │ ├── disjunction.rb │ ├── flipable.rb │ ├── liftable.rb │ ├── mzero.rb │ ├── op.rb │ ├── proc_convertable.rb │ ├── revapply.rb │ ├── version.rb │ └── with_args.rb └── spec ├── class_spec.rb ├── context_spec.rb ├── disjunction_spec.rb ├── lambda_spec.rb ├── method_spec.rb ├── op_spec.rb ├── proc_spec.rb ├── revapply_spec.rb ├── shared ├── composable_spec.rb └── liftable.rb ├── spec_helper.rb ├── symbol_spec.rb └── unbound_method_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | vendor/ 19 | .rbenv-version 20 | .ruby-version 21 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | --exclude-pattern "spec/shared/*_spec.rb" 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.3 5 | - 2.0.0 6 | # - 2.1.0 7 | - 2.2.0 8 | - 2.3.0 9 | - 2.4.0 10 | - 2.5.0 11 | - 2.6.0 12 | script: bundle exec rspec spec 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in lambda_driver.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tomohito Ozaki 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LambdaDriver 2 | 3 | 虚弦斥力場生成システム 4 | 5 | LambdaDriver drives your code more functional. 6 | 7 | ```ruby 8 | # [:foo, :bar, :baz].map{|s| s.to_s }.map{|s| s.upcase } 9 | # [:foo, :bar, :baz].map(&:to_s).map(&:upcase) 10 | 11 | [:foo, :bar, :baz].map(&:to_s >> :upcase ) # => ["FOO", "BAR", "BAZ"] 12 | ``` 13 | 14 | ```ruby 15 | # [:foo, :hoge, :bar, :fuga].select{|s| s.to_s.length > 3} # => [:hoge, :fuga] 16 | 17 | [:foo, :hoge, :bar, :fuga].select(&:to_s >> :length >> 3._(:<)) # => [:hoge, :fuga] 18 | ``` 19 | 20 | - [project page](http://yuroyoro.github.com/lambda_driver/) 21 | - [rubygems.org](https://rubygems.org/gems/lambda_driver) 22 | 23 | ## Build status 24 | 25 | [![Build Status](https://travis-ci.org/yuroyoro/lambda_driver.png)](https://travis-ci.org/yuroyoro/lambda_driver) 26 | 27 | ## Installation 28 | 29 | Add this line to your application's Gemfile: 30 | 31 | gem 'lambda_driver' 32 | 33 | And then execute: 34 | 35 | $ bundle 36 | 37 | Or install it yourself as: 38 | 39 | $ gem install lambda_driver 40 | 41 | ## Usage 42 | 43 | ### Proc/lambda/Symbol/Method extensions 44 | - call 45 | - compose 46 | - with_args 47 | - flip 48 | - curry 49 | 50 | #### alias :< to Proc#call 51 | 52 | ```ruby 53 | f = lambda{|x| x.to_s } 54 | f < :foo # => "foo" 55 | ``` 56 | 57 | #### alias :+@ to Proc#to_proc 58 | 59 | ```ruby 60 | +:to_s # => # 61 | +:to_s < :foo # => "foo" 62 | ``` 63 | 64 | 65 | #### Proc#compose 66 | 67 | Returns new lambda which composed self and given function. 68 | A composed proc called with args, executes `self.(g(*args)). 69 | 70 | ```ruby 71 | f = lambda{|x| x.to_s * 2 } 72 | g = lambda{|y| y.length } 73 | 74 | h = f.compose g # => # 75 | h.(:hoge) # => "44" ( == f.call(g.call(:hoge)) ) 76 | ``` 77 | 78 | This method is aliased as `<<`. 79 | 80 | ```ruby 81 | f << g # => f.compose(g) 82 | f << g < :hoge # => "44" ( == f.call(g.call(:hoge)) ) 83 | ``` 84 | 85 | #### Proc#lift 86 | 87 | Lift this function to the given context-function. 88 | The lifted function can compose other function with context-function. 89 | 90 | The given context-fuction used by `compose_with_lifting` 91 | to compose other function. 92 | 93 | The context-function should recieve 2 arguments. 94 | 95 | - first one is a function that reciver function of `compose_with_lifting` method. 96 | - second arg is a result of g(x) 97 | - g is a function passed to `compose_with_lifting` 98 | 99 | If given arguments is Symbol, find context-function from 100 | default context-functions. 101 | 102 | - :identify 103 | - this context nothing to do 104 | - :maybe 105 | - computations which may not return a result 106 | - :list 107 | - computations which can return multiple possible results 108 | - :reader 109 | - computations which read from a shared environment 110 | - :writer 111 | - computations which write data in addition to computing values 112 | 113 | see -> LambdaDriver::Context 114 | 115 | #### Proc#compose_with_lifting 116 | 117 | Compose self and given function on the context-function. 118 | The context-function is passed by `lift` method. 119 | 120 | This method returns composed function like bellow. 121 | 122 | ```ruby 123 | lambda{|args| context(self, g(*args)) } 124 | ``` 125 | 126 | For example, set context-function that logging the result. 127 | 128 | ```ruby 129 | hash = {:a => "foo"} 130 | f = lambda{|x| x.length} 131 | g = lambda{|y| hash[y]} 132 | 133 | ctx = lambda{|f,x| 134 | puts "g(x) -> #{x}" 135 | y = f.call(x) 136 | puts "f(g(x)) -> #{y}" 137 | y 138 | } 139 | 140 | lifted = f.lift(ctx) 141 | h = lifted.compose_with_lifting g 142 | 143 | h.(:a) 144 | #=> g(x) -> foo # output by ctx 145 | #=> f(g(x)) -> 3 # output by ctx 146 | #=> 3 147 | ``` 148 | 149 | if context-function does not given, 150 | default behaivior is compose function with checking g(x) is mzero 151 | 152 | if g(x) is mzero, it does not call self and return g(x), 153 | otherwise returns f(g(x)). 154 | 155 | mzero means the object is nil or emtpy 156 | 157 | ```ruby 158 | hash = {:a => "foo"} 159 | f = lambda{|y| y.length } 160 | g = lambda{|y| hash[y]} 161 | h = f.compose_with_lifting g 162 | h.(:a) # => 3 163 | h.(:b) # => nil (it does not called f) 164 | ``` 165 | 166 | This method is aliased as `<=`. 167 | 168 | ```ruby 169 | f <= g # => f.compose_with_lifting(g) 170 | ``` 171 | 172 | ##### Example : try-chains 173 | 174 | An annoying try chain like `arr.try(:first).try(:upcase).try(:to_sym)` is rewritten by following 175 | 176 | ```ruby 177 | arr = ["foo", "bar"] 178 | arr.try(&:first >= :upcase >= :to_sym) # => :Foo 179 | 180 | arr = [nil] 181 | arr.try(&:first >= :upcase >= :to_sym) # => nil 182 | ``` 183 | 184 | #### Proc#with_args 185 | 186 | Returns partially applied function that has 2nd and more parameters are 187 | fixed by given *args. 188 | 189 | ```ruby 190 | f = lambda{|x, y, z| [x, y, z]} 191 | 192 | h = f.with_args(:a, :b) # => # 193 | h.(:c) # => [:c, :a, :b] ( == f.call(:c, :a, :b) ) 194 | ``` 195 | 196 | This method is aliased as `*`. 197 | 198 | ```ruby 199 | f = lambda{|x, y| [x, y]} 200 | 201 | f * :foo # => # (== f.with_args(:foo) ) 202 | f * :foo < :bar # => [:bar, :foo] ( == f.with_args(:foo).call(:bar) ) 203 | ``` 204 | 205 | 206 | #### Proc#flip 207 | 208 | Returns function whose parameter order swaped 1st for 2nd. 209 | A result of filped fuction is curried by Proc#curry. 210 | 211 | ```ruby 212 | f = lambda{|x, y, z| [x, y, z]} 213 | 214 | h = f.flip # => # 215 | h.call(:a).call(:b).call(:c) # => [:b, :a, :c] (== f.curry.call(:b).call(:a).call(:b)) 216 | h < :a < :b < :c # => [:b, :a, :c] (== f.curry.call(:b).call(:a).call(:b)) 217 | ``` 218 | 219 | If arguments is var-args, pass explicitly arity to curring. 220 | 221 | ```ruby 222 | p = Proc.new{|*args| args.inspect } 223 | 224 | p.arity # => -1 225 | p.flip(3).call(:a).(:b).(:c) # => "[:b, :a, :c]" 226 | p.flip(4).call(:a).(:b).(:c).(:d) # => "[:b, :a, :c, :d]" 227 | ``` 228 | 229 | If arity is 0 or 1, flip returns itself. 230 | 231 | This method is aliased as `~@`. 232 | 233 | ```ruby 234 | ~f # => # (== f.filp) 235 | ~f < :a < :b < :c # => [:b, :a, :c] (== f.filp.call(:b).call(:a).call(:b)) 236 | 237 | ``` 238 | 239 | ### Symbol extensions 240 | - to_method 241 | - to_method_with_args 242 | 243 | #### Symbol#to_method 244 | 245 | Symbol#to_method generates a function that extract Method object from given argument. 246 | This method is aliased as `-@`. 247 | 248 | ```ruby 249 | (-:index).call("foobarbaz") # => # 250 | (-:index).call("foobarbaz").call("bar") # => 3 (== "foobarbaz".index("bar") ) 251 | 252 | -:index < "foobarbaz" # => # 253 | -:index < "foobarbaz" < "bar" # => 3 (== "foobarbaz".index("bar") ) 254 | ``` 255 | 256 | #### Symbol#to_method_with_args 257 | 258 | Symbol#to_method_with_args generates a function that extract Method object from given object, 259 | and returns function is partially applied parameters by passed arguments. 260 | It is same as Symbol#to_method with Proc#with_args. 261 | 262 | This method is aliased as `&`. 263 | 264 | ```ruby 265 | :index.to_method_with_args("bar") # => # 3 (== "foobarbaz".index("bar") ) 267 | 268 | :index & "bar" # => # 3 (== "foobarbaz".index("bar") ) 270 | ``` 271 | 272 | ### Class extensions 273 | - alias instance_method, :/ 274 | 275 | ```ruby 276 | String / :index # => # 277 | ``` 278 | 279 | ### UnboundMethod extensions 280 | - alias bind, :< 281 | 282 | ```ruby 283 | String / :index # => # 284 | String / :index < "foobarbaz" # => # 285 | String / :index < "foobarbaz" < 3 # => 3 (== "foobarbaz".index("bar") ) 286 | ``` 287 | 288 | ### Combinators 289 | 290 | - ski combinator 291 | 292 | ### Object extensions 293 | - obj.revapply(|>) 294 | - obj._ 295 | - obj.disjunction(f) 296 | 297 | #### Object#revapply 298 | 299 | `Object#revapply` is applies self to given proc/block. 300 | 301 | ```ruby 302 | f = lambda{|x| x * 2 } 303 | 304 | "foo".revapply(f) # => "foofoo" (== f.call("foo") ) 305 | ``` 306 | 307 | #### Object#_ 308 | 309 | Object#_ is shortcut to quickly extract Method object. 310 | 311 | ```ruby 312 | "foobarbaz"._.index # => # 313 | "foobarbaz"._.index < "bar" # => 3 (== "foobarbaz".index("bar") ) 314 | 315 | 2._(:>=) # => #=> 316 | [1, 2, 3].select(&2._(:>=)) # => [1, 2]( = [1, 2].select{|n| 2 >= n}) 317 | ``` 318 | 319 | #### Object#disjunction 320 | 321 | `Object#disjunction` select self or result of applied self to given function. 322 | if f(self) is nil, returns self, otherwise return f(self). 323 | 324 | ```ruby 325 | f = lambda{|x| x % 2 == 0 ? nil : x * 2} 326 | 327 | 2.disjunction(f) # => 2 (disjunction returns receiver object) 328 | 3.disjunction(f) # => 6 (disjunction returns f(3) ) 329 | ``` 330 | 331 | ## Contributing 332 | 333 | 1. Fork it 334 | 2. Create your feature branch (`git checkout -b my-new-feature`) 335 | 3. Commit your changes (`git commit -am 'Add some feature'`) 336 | 4. Push to the branch (`git push origin my-new-feature`) 337 | 5. Create new Pull Request 338 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new("spec") 5 | -------------------------------------------------------------------------------- /lambda_driver.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'lambda_driver/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "lambda_driver" 8 | gem.version = LambdaDriver::VERSION 9 | gem.authors = ["Tomohito Ozaki"] 10 | gem.email = ["ozaki@yuroyoro.com"] 11 | gem.description = %q{Drives your code more functioal!} 12 | gem.summary = %q{Drives your code more functioal!} 13 | gem.homepage = "http://yuroyoro.github.com/lambda_driver/" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | # dependencies 21 | gem.add_development_dependency 'rake' 22 | gem.add_development_dependency 'rspec' 23 | end 24 | -------------------------------------------------------------------------------- /lib/lambda_driver.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require "lambda_driver/version" 3 | Dir["#{File.dirname(__FILE__)}/lambda_driver/*.rb"].sort.each do |path| 4 | next if File.basename(path, '.rb') == 'core_ext' 5 | require "lambda_driver/#{File.basename(path, '.rb')}" 6 | end 7 | require "lambda_driver/core_ext" 8 | 9 | module LambdaDriver 10 | 11 | # SKI combinators 12 | I = lambda{|x| x } 13 | K = lambda{|x| lambda{|y| y }} 14 | S = lambda{|x| lambda{|y| lambda{|z| x.to_proc.call(z).call(y.to_proc.call(z)) } } } 15 | 16 | # Boolean combinators 17 | AND = lambda{|l| lambda{|r| l && r }} 18 | OR = lambda{|l| lambda{|r| l || r }} 19 | 20 | class << self 21 | def i ; I end 22 | def k ; K end 23 | def s ; S end 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /lib/lambda_driver/callable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Callable 3 | 4 | def <(*args, &block) 5 | if block_given? 6 | self.to_proc.call(*args, &block) 7 | else 8 | self.to_proc.call(*args) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/lambda_driver/composable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Composable 3 | if RUBY_VERSION < "2.6.0" 4 | # Returns new lambda which composed self and given function. 5 | # A composed proc called with args, executes `self.(g(*args)). 6 | # 7 | # f = lambda{|x| x.to_s } 8 | # g = lambda{|y| y.length } 9 | # h = f compose g 10 | # h.(:hoge) # => 4 11 | # 12 | # This method is aliased as `<<`. 13 | # 14 | # f << g # => f.compose(g) 15 | # 16 | def compose(g) 17 | lambda{|*args| 18 | self.to_proc.call(g.to_proc.call(*args)) 19 | } 20 | end 21 | 22 | # g compose self 23 | def >>(g) 24 | g.to_proc << self 25 | end 26 | 27 | def self.included(klass) 28 | klass.send(:alias_method, :<<, :compose) 29 | end 30 | else 31 | # Returns new lambda which composed self and given function. 32 | # A composed proc called with args, executes `self.(g(*args)). 33 | # 34 | # f = lambda{|x| x.to_s } 35 | # g = lambda{|y| y.length } 36 | # h = f compose g 37 | # h.(:hoge) # => 4 38 | # 39 | # This method is aliased as `<<`. 40 | # 41 | # f << g # => f.compose(g) 42 | # 43 | def compose(g) 44 | self.to_proc << g.to_proc 45 | end 46 | 47 | # g compose self 48 | def >>(g) 49 | g.to_proc << self 50 | end 51 | 52 | def self.included(klass) 53 | klass.send(:alias_method, :<<, :compose) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/lambda_driver/context.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Context 3 | 4 | Identify = lambda{|f, x| f.call(x) } 5 | 6 | Maybe = lambda{|f, x| 7 | mzero_method = x.respond_to?(:mzero?) ? :mzero? : :nil? 8 | x.send(mzero_method) ? x : f.call(x) 9 | } 10 | 11 | List = lambda{|f, x| x.map(&f) } 12 | 13 | Reader = lambda{|f, (r, x)| 14 | # set reader env to thread local 15 | Thread.current[:lambda_driver_reader_ctx_ask] = r 16 | # define ask method to access context of reader 17 | f.binding.eval(<<-CODE) 18 | def ask 19 | Thread.current[:lambda_driver_reader_ctx_ask] 20 | end 21 | CODE 22 | 23 | begin 24 | [r, f.call(x)] 25 | ensure 26 | # tear down reader context 27 | Thread.current[:lambda_driver_reader_ctx_ask] = nil 28 | f.binding.eval("undef ask") 29 | end 30 | } 31 | 32 | Writer = lambda{|f, (x, w)| 33 | pool = w || [] 34 | 35 | # set writer pool to thread local 36 | Thread.current[:lambda_driver_writer_ctx_pool] = pool 37 | # define tell method to write log to context 38 | f.binding.eval(<<-CODE) 39 | def tell(s) 40 | pool = Thread.current[:lambda_driver_writer_ctx_pool] 41 | pool << s 42 | end 43 | CODE 44 | 45 | begin 46 | [f.call(x), pool] 47 | ensure 48 | # tear down reader context 49 | Thread.current[:lambda_driver_writer_ctx_pool] = nil 50 | f.binding.eval("undef tell") 51 | end 52 | } 53 | 54 | def self.[](name) 55 | name = name.to_s.capitalize 56 | const_defined?(name) && const_get(name) 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path| 3 | require "lambda_driver/core_ext/#{File.basename(path, '.rb')}" 4 | end 5 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/class.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class Class 3 | alias_method :/, :instance_method 4 | end 5 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/method.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class Method 3 | include LambdaDriver::Callable 4 | include LambdaDriver::Composable 5 | include LambdaDriver::WithArgs 6 | include LambdaDriver::Flipable 7 | include LambdaDriver::ProcConvertable 8 | include LambdaDriver::Currying 9 | include LambdaDriver::Liftable 10 | end 11 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/object.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class Object 3 | include LambdaDriver::Op::Proxy 4 | include LambdaDriver::Revapply 5 | include LambdaDriver::Disjunction 6 | include LambdaDriver::Mzero 7 | end 8 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/proc.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | 3 | # Override Proc#>> and << these are implemented from Ruby 2.6 4 | # to ensure passed argument is callable. 5 | # Convert argument to proc obj by calling :to_proc before call super implementaion 6 | module ProcOverride 7 | def compose(g) 8 | self << g 9 | end 10 | 11 | # g compose self 12 | def <<(g) 13 | super(g.to_proc) 14 | end 15 | 16 | # g compose self 17 | def >>(g) 18 | g.to_proc << self 19 | end 20 | end 21 | 22 | class Proc 23 | include LambdaDriver::Callable 24 | include LambdaDriver::WithArgs 25 | include LambdaDriver::Flipable 26 | include LambdaDriver::ProcConvertable 27 | include LambdaDriver::Currying 28 | include LambdaDriver::Liftable 29 | 30 | if RUBY_VERSION < "2.6.0" 31 | include LambdaDriver::Composable 32 | else 33 | prepend ProcOverride 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/symbol.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class Symbol 3 | include LambdaDriver::Callable 4 | include LambdaDriver::Composable 5 | include LambdaDriver::WithArgs 6 | include LambdaDriver::Flipable 7 | include LambdaDriver::ProcConvertable 8 | include LambdaDriver::Currying 9 | include LambdaDriver::Liftable 10 | 11 | def to_method 12 | lambda{|obj| obj._(self) } 13 | end 14 | alias_method :-@, :to_method 15 | 16 | def to_method_with_args(*args) 17 | lambda{|obj| obj._(self).call(*args) } 18 | end 19 | alias_method :&, :to_method_with_args 20 | end 21 | -------------------------------------------------------------------------------- /lib/lambda_driver/core_ext/unbound_method.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | class UnboundMethod 3 | alias_method :<, :bind 4 | end 5 | -------------------------------------------------------------------------------- /lib/lambda_driver/currring.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Curring 3 | def curry(arity = nil) 4 | self.to_proc.curry(arity) 5 | end 6 | 7 | def self.included(klass) 8 | klass.send(:alias_method, :%, :curry) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/lambda_driver/currying.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Currying 3 | 4 | if RUBY_VERSION < '1.9.0' 5 | def curry(arity = nil) 6 | f = self.to_proc 7 | arity ||= __arity(f) 8 | return f if arity == 0 9 | 10 | lambda{|arg| __curry(f, arity, arg, []) } 11 | end 12 | else 13 | def curry(arity = nil) 14 | self.to_proc.curry(arity) 15 | end 16 | end 17 | 18 | private 19 | def __arity(f) 20 | (f.arity >= 0) ? f.arity : -(f.arity + 1) 21 | end 22 | 23 | def __curry(f, arity, arg, passed) 24 | args = passed + [arg] 25 | return f.call(*args) if arity == 1 26 | lambda{|arg| __curry(f, arity - 1, arg, args) } 27 | end 28 | 29 | 30 | def self.included(klass) 31 | klass.send(:alias_method, :%, :curry) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/lambda_driver/disjunction.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Disjunction 3 | def disjunction(f = nil, &block) 4 | if f.nil? && (not block_given?) 5 | return self.method(:disjunction) 6 | end 7 | 8 | (block_given? ? (yield self) : f.call(self)) || self 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/lambda_driver/flipable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Flipable 3 | # Returns function whose parameter order spawed 1st for 2nd. 4 | # A result of filped fuction is curried by Proc#curry. 5 | # 6 | # f = lambda{|x, y, z| [x, y, z]} 7 | # h = f.flip 8 | # h.(:a).(:b).(:c) # => [:b, :a, :c] 9 | # 10 | # If arguments is var-args, pass explicitly arity to curring. 11 | # 12 | # p = Proc.new{|*args| args.inspect } 13 | # p.flip(3).call(:a).(:b).(:c) # => "[:b, :a, :c]" 14 | # p.flip(4).call(:a).(:b).(:c).(:d) # => "[:b, :a, :c, :d]" 15 | # 16 | # If arity is 0 or 1, flip returns itself. 17 | # 18 | # This method is aliased as `~@`. 19 | # 20 | # ~f # => f.filp 21 | # 22 | def flip(arity = nil) 23 | f = self.to_proc 24 | return self if (0..1).include?(f.arity) 25 | return self if f.arity == -1 && arity.nil? 26 | 27 | curried = f.curry(arity) 28 | lambda{|x| 29 | lambda{|y| 30 | g = curried[y] 31 | (g.respond_to? :call) ? g[x] : g 32 | } 33 | } 34 | end 35 | 36 | def self.included(klass) 37 | klass.send(:alias_method, :~@, :flip) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/lambda_driver/liftable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Liftable 3 | 4 | # Compose self and given function on the context-function. 5 | # The context-funciton is passed by `lift` method. 6 | # 7 | # This method returns composed funciton like bellow. 8 | # 9 | # lambda{|args| context.call(self, context.call(g,*args) } 10 | # 11 | # For example, set context-function that logging the result. 12 | # 13 | # hash = {:a => "foo"} 14 | # f = lambda{|x| x.length} 15 | # g = lambda{|y| hash[y]} 16 | # 17 | # ctx = lambda{|f,x| 18 | # res = f.call(x) 19 | # puts "result -> #{res}" 20 | # res 21 | # } 22 | # 23 | # lifted = f.lift(ctx) 24 | # h = lifted.compose_with_lifting g 25 | # 26 | # h.(:a) 27 | # #=> result -> foo 28 | # #=> result -> 3 29 | # #=> 3 30 | # 31 | # if context-function does not given, 32 | # default behaivior is compose function with checking g(x) is mzoro 33 | # 34 | # if g(x) is mzero, it does not call self and return g(x), 35 | # otherwise returns f(g(x)). 36 | # 37 | # mzero means the object is nil or emtpy 38 | # 39 | # hash = {:a => "foo"} 40 | # f = lambda{|y| y.length } 41 | # g = lambda{|y| hash[y]} 42 | # h = f.compose_with_lifting g 43 | # h.(:a) # => 3 44 | # h.(:b) # => nil (it does not called f) 45 | # 46 | # This method is aliased as `<=`. 47 | # 48 | # f <= g # => f.compose_with_lifting(g) 49 | # 50 | def compose_with_lifting(g) 51 | if @lambda_driver_lifted 52 | ctx = @lambda_driver_liftable_context 53 | self.compose(g).tap{|f| 54 | f.instance_eval do 55 | @lambda_driver_lifted = true 56 | @lambda_driver_liftable_context = ctx 57 | end 58 | } 59 | else 60 | self.lift(DEFAULT_CONTEXT).compose_with_lifting(g) 61 | end 62 | end 63 | 64 | # This is a default context-function. 65 | # default behaivior is compose function with checking g(x) is mzoro 66 | # 67 | # if g(x) is mzero, it does not call self and return g(x), 68 | # otherwise returns f(g(x)). 69 | # 70 | # mzero means the object is nil or emtpy 71 | # 72 | # hash = {:a => "foo"} 73 | # f = lambda{|y| y.length } 74 | # g = lambda{|y| hash[y]} 75 | # h = f.compose_with_lifting g 76 | # h.(:a) # => 3 77 | # h.(:b) # => nil (it does not called f) 78 | # 79 | DEFAULT_CONTEXT = LambdaDriver::Context[:maybe] 80 | 81 | # Lift this function to the given context-function. 82 | # The lifted fucntion can compose other function with context-fucntion. 83 | # 84 | # The given context-fuction used by `compose_with_lifting` 85 | # to compose other fucntion. 86 | # 87 | # The context-funciton should recieve 2 arguments. 88 | # - first one is a function that reciver function of `compose_with_lifting` method. 89 | # - second arg is a result of g(x) 90 | # -- g is a function passed to `compose_with_lifting` 91 | # 92 | def lift(ctx = DEFAULT_CONTEXT, &block) 93 | ctx = block_given? ? block : ctx 94 | ctx = case ctx 95 | when String, Symbol then LambdaDriver::Context[ctx] 96 | else ctx 97 | end 98 | 99 | # do not lift same context again 100 | return self if lambda_driver_lifted? && (ctx == lambda_driver_liftable_context) 101 | 102 | lambda{|*args| ctx.call(self, *args) }.tap{|f| 103 | f.instance_eval do 104 | @lambda_driver_lifted = true 105 | @lambda_driver_liftable_context = ctx 106 | end 107 | } 108 | end 109 | 110 | def lambda_driver_lifted? 111 | @lambda_driver_lifted 112 | end 113 | 114 | def lambda_driver_liftable_context 115 | @lambda_driver_liftable_context || DEFAULT_CONTEXT 116 | end 117 | 118 | def >=(g) 119 | g.to_proc.lift(lambda_driver_liftable_context).compose_with_lifting(self) 120 | end 121 | 122 | def self.included(klass) 123 | klass.send(:alias_method, :<=, :compose_with_lifting) 124 | klass.send(:alias_method, :ymsr, :lift) 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/lambda_driver/mzero.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Mzero 3 | def mzero? 4 | mzero_method = self.respond_to?(:empty?) ? :empty? : :nil? 5 | self.send(mzero_method) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/lambda_driver/op.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Op 3 | module Proxy 4 | def _(method = nil) 5 | method ? ::LambdaDriver::Op.method_or_lambda(self, method) : OpProxy.new(self) 6 | end 7 | end 8 | 9 | def method_or_lambda(obj, method) 10 | (obj.respond_to?(method) && obj.method(method)) || 11 | lambda{|*args| obj.__send__(method, *args) } 12 | end 13 | module_function :method_or_lambda 14 | 15 | class BlankSlate 16 | instance_methods.each { |m| undef_method m unless m =~ /^(__|object_id)/ } 17 | end 18 | 19 | class OpProxy < (RUBY_VERSION < '1.9.0' ? BlankSlate : BasicObject) 20 | def initialize(obj) 21 | @obj = obj 22 | end 23 | 24 | def call(method) 25 | ::LambdaDriver::Op.method_or_lambda(@obj, method) 26 | end 27 | 28 | def method_missing(method) 29 | ::LambdaDriver::Op.method_or_lambda(@obj, method) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/lambda_driver/proc_convertable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::ProcConvertable 3 | def self.included(klass) 4 | klass.send(:alias_method, :+@, :to_proc) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/lambda_driver/revapply.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::Revapply 3 | def revapply(f = nil, &block) 4 | if f.nil? && (not block_given?) 5 | return self.method(:revapply) 6 | end 7 | 8 | block_given? ? (yield self) : f.call(self) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/lambda_driver/version.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver 3 | VERSION = "1.3.0" 4 | end 5 | -------------------------------------------------------------------------------- /lib/lambda_driver/with_args.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | module LambdaDriver::WithArgs 3 | 4 | # Returns partially applied function that has 2nd and more parameters 5 | # fixed by given *args. 6 | # 7 | # f = lambda{|x, y, z| [x, y, z]} 8 | # h = f.with_args(:a, :b) 9 | # h.(:c) # => [:c, :a, :b] 10 | # 11 | # This method is aliased as `*`. 12 | # 13 | # f * :foo # => f.with_args(:foo) 14 | # 15 | def with_args(*args) 16 | lambda{|v| 17 | self.to_proc.call(*([v] + args)) 18 | } 19 | end 20 | 21 | def self.included(klass) 22 | klass.send(:alias_method, :*, :with_args) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/class_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe 'class' do 5 | describe './' do 6 | subject { String / :index } 7 | 8 | it { should be_a_kind_of UnboundMethod } 9 | it("(klass / method_name).bind(obj).call(x) should be obj.method_name(x)"){ 10 | subject.bind("foobarbaz").call("bar") == "foobarbaz".index("bar") 11 | } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/context_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe LambdaDriver::Context do 5 | 6 | let(:f){ lambda{|x| x.to_s} } 7 | let(:g){ lambda{|x| x * 2} } 8 | 9 | describe LambdaDriver::Context::Identify do 10 | let(:ctx) { :identify } 11 | let(:x){ :foo } 12 | it("f.lift(:identify).call(x).should == f(x)"){ 13 | f.lift(:identify).call(x).should == f.call(x) 14 | } 15 | 16 | it("(f.lift(:identify) >= g).call(x).should == g(f(x))"){ 17 | (f.lift(:identify) >= g).call(x).should == g.call(f.call(x)) 18 | } 19 | end 20 | 21 | describe LambdaDriver::Context::Maybe do 22 | let(:x){ :foo } 23 | 24 | it("f.lift(:maybe).call(nil).should be_nil"){ 25 | f.lift(:maybe).call(nil).should be_nil 26 | } 27 | 28 | it("f.lift(:maybe).call(x).should f(x)"){ 29 | f.lift(:maybe).call(x).should == f.call(x) 30 | } 31 | 32 | it("(f.lift(:maybe) >= g).call(nil).should be_nil "){ 33 | (f.lift(:maybe) >= g).call(nil).should be_nil 34 | } 35 | 36 | it("(f.lift(:maybe) >= g).call(x).should == g(f(x))"){ 37 | (f.lift(:maybe) >= g).call(x).should == g.call(f.call(x)) 38 | } 39 | end 40 | 41 | describe LambdaDriver::Context::List do 42 | let(:x){ [:foo, :bar] } 43 | 44 | it("f.lift(:list).call([]).should == []"){ 45 | f.lift(:list).call([]).should == [] 46 | } 47 | 48 | it("f.lift(:list).call(x).should == x.map(&f)"){ 49 | f.lift(:list).call(x).should == x.map(&f) 50 | } 51 | 52 | it("(f.lift(:list) >= g).call([]).should == (x.map(&f)).map(&g)"){ 53 | (f.lift(:list) >= g).call([]).should == [] 54 | } 55 | 56 | it("(f.lift(:list) >= g).call(x).should == (x.map(&f)).map(&g)"){ 57 | (f.lift(:list) >= g).call(x).should == (x.map(&f)).map(&g) 58 | } 59 | end 60 | 61 | describe LambdaDriver::Context::Reader do 62 | let(:x){ 2 } 63 | let(:r){ 3 } 64 | 65 | let(:f){ lambda{|x| (ask + x).to_s} } 66 | let(:g){ lambda{|x| x * (ask)} } 67 | 68 | it("f.lift(:reader).call([r, x]).should == [r, f(x)]"){ 69 | f.lift(:reader).call([r, x]).should == [r, (r + x).to_s] 70 | } 71 | 72 | it("(f.lift(:reader) >= g).call([r, x]).should == [r, g(f(x))]"){ 73 | (f.lift(:reader) >= g).call([r, x]).should == [r, ((r + x).to_s) * r] 74 | } 75 | end 76 | 77 | describe LambdaDriver::Context::Writer do 78 | let(:w) { [:hoge] } 79 | let(:x) { :foo } 80 | let(:f){ lambda{|x| tell("bar_" + x.to_s); x.to_s } } 81 | let(:g){ lambda{|x| tell("baz_" + x); x * 2 } } 82 | 83 | it("f.lift(:writer).call([x, w]).should == [f(x), w]"){ 84 | f.lift(:writer).call([x, w]).should == [x.to_s, w] 85 | w.should == [:hoge, "bar_foo"] 86 | } 87 | 88 | it("(f.lift(:writer) >= g).call([r, x]).should == [r, g(f(x))]"){ 89 | (f.lift(:writer) >= g).call([x, w]).should == [(x.to_s) * 2, w] 90 | w.should == [:hoge, "bar_foo", "baz_foo"] 91 | } 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/disjunction_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe LambdaDriver::Disjunction do 5 | let(:obj) { "foobarbaz" } 6 | let(:f) { lambda{|x| x * 2 } } 7 | 8 | describe '#disjunction'do 9 | context 'f(obj) returns nil' do 10 | context 'given none' do 11 | subject { obj.disjunction } 12 | 13 | it { should be_a_kind_of Method } 14 | 15 | it('obj.disjunction.call(f) should be obj'){ 16 | subject.call(lambda{|x| nil}).should == obj 17 | } 18 | it('obj.disjunction.call{block} should be obj'){ 19 | subject.call{nil}.should == obj 20 | } 21 | it('obj.disjunction.call(f){block} should obj'){ 22 | subject.call(f){nil}.should == obj 23 | } 24 | end 25 | 26 | context 'block given' do 27 | it('obj.disjunction{block} should be result of execute block with obj'){ 28 | obj.disjunction{nil}.should == obj 29 | } 30 | end 31 | 32 | context 'proc object given' do 33 | it('obj.ap(f) should be f.call(obj)'){ 34 | obj.disjunction(lambda{|x| nil}).should == obj 35 | } 36 | end 37 | 38 | context 'block given and proc object given' do 39 | it('obj.ap.call(f){block} should be result of execute block with obj'){ 40 | obj.disjunction(f){nil}.should == obj 41 | } 42 | end 43 | end 44 | 45 | context 'f(obj) does not returns nil' do 46 | context 'given none' do 47 | subject { obj.disjunction } 48 | 49 | it { should be_a_kind_of Method } 50 | 51 | it('obj.disjunction.call(f) should be obj'){ 52 | subject.call(f).should == f.call(obj) 53 | } 54 | it('obj.disjunction.call{block} should be result of execute block with obj'){ 55 | subject.call{|x| x * 3 }.should == obj * 3 56 | } 57 | it('obj.disjunction.call(f){block} should be result of execute block with obj'){ 58 | subject.call(f){|x| x * 3 }.should == obj * 3 59 | } 60 | end 61 | 62 | context 'block given' do 63 | it('obj.disjunction{block} should be result of execute block with obj'){ 64 | obj.disjunction{|x| x * 3 }.should == (obj * 3) 65 | } 66 | end 67 | 68 | context 'proc object given' do 69 | it('obj.ap(f) should be f.call(obj)'){ 70 | obj.disjunction(f).should == f.call(obj) 71 | } 72 | end 73 | 74 | context 'block given and proc object given' do 75 | it('obj.ap.call(f){block} should be result of execute block with obj'){ 76 | obj.disjunction(f){|x| x * 3 }.should == (obj * 3) 77 | } 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/lambda_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe 'lambda' do 5 | describe '#compose' do 6 | subject { lambda{|x| x.to_s} } 7 | 8 | it_should_behave_like 'composable' 9 | end 10 | 11 | describe '#compose_with_lifting' do 12 | subject { lambda{|x| x.mzero? ? x : x.to_s} } 13 | 14 | it_should_behave_like 'liftable' 15 | end 16 | 17 | describe '#with_args' do 18 | subject { lambda{|x, y, *z| [x, y] + z.to_a } } 19 | it_should_behave_like 'with_args' 20 | end 21 | 22 | describe '#flip' do 23 | context 'arity = 1' do 24 | subject { lambda{|x| [x] } } 25 | 26 | it_should_behave_like 'flip(arity=1)' 27 | end 28 | 29 | context 'arity > 1' do 30 | subject { lambda{|x, y| [x, y] } } 31 | 32 | it_should_behave_like 'flip' 33 | end 34 | 35 | context 'varargs' do 36 | subject { lambda{|*x| x } } 37 | 38 | it_should_behave_like 'flip(varargs)' 39 | end 40 | end 41 | 42 | describe '#curry' do 43 | subject { lambda{|x, y| [x, y] } } 44 | 45 | it_should_behave_like 'curry' 46 | 47 | context 'varargs' do 48 | subject { lambda{|*x| x } } 49 | 50 | it_should_behave_like 'curry(varargs)' 51 | end 52 | end 53 | 54 | describe '#call' do 55 | subject { lambda{|x| x.to_s + "_f"} } 56 | 57 | it_should_behave_like 'call' 58 | it_should_behave_like 'call(<)' 59 | end 60 | 61 | describe 'ailases' do 62 | subject { lambda{|x| x.to_s + "_f"} } 63 | 64 | it_should_behave_like 'aliases' 65 | it_should_behave_like 'aliases(varargs)' do 66 | subject { lambda{|*x| x } } 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/method_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe Method do 5 | describe '#compose' do 6 | subject { "aaa".method(:+) } 7 | 8 | it_should_behave_like 'composable' do 9 | let(:x) { "foo" } 10 | end 11 | end 12 | 13 | describe '#with_args' do 14 | subject { "foobarbaz".method(:delete) } 15 | 16 | it_should_behave_like 'with_args' do 17 | let(:x) { 'r' } 18 | let(:y) { 'o' } 19 | let(:z) { 'a' } 20 | end 21 | end 22 | 23 | describe '#flip' do 24 | context 'arity = 1' do 25 | subject { "aaa".method(:+) } 26 | 27 | it_should_behave_like 'flip(arity=1)' do 28 | let(:x) { "foo" } 29 | let(:y) { "bar" } 30 | end 31 | end 32 | 33 | context 'arity > 1' do 34 | subject { "foobarbaz".method(:tr) } 35 | 36 | it_should_behave_like 'flip' do 37 | let(:x) { 'r' } 38 | let(:y) { 'o' } 39 | end 40 | end 41 | 42 | context 'varargs' do 43 | subject { "foobarbaz".method(:delete) } 44 | 45 | it_should_behave_like 'flip(varargs)' do 46 | let(:x) { 'r' } 47 | let(:y) { 'o' } 48 | end 49 | end 50 | end 51 | 52 | describe '#curry' do 53 | context 'arity > 1' do 54 | subject { "foobarbaz".method(:tr) } 55 | 56 | it_should_behave_like 'curry' do 57 | let(:x) { 'r' } 58 | let(:y) { 'o' } 59 | end 60 | end 61 | 62 | context 'varargs' do 63 | subject { "foobarbaz".method(:delete) } 64 | 65 | it_should_behave_like 'curry(varargs)' do 66 | let(:x) { 'r' } 67 | let(:y) { 'o' } 68 | end 69 | end 70 | end 71 | 72 | describe '#call' do 73 | subject { "aaa".method(:+) } 74 | 75 | it_should_behave_like 'call' do 76 | let(:x) { "foo" } 77 | end 78 | it_should_behave_like 'call(<)' do 79 | let(:x) { "foo" } 80 | end 81 | end 82 | 83 | describe 'ailases' do 84 | it_should_behave_like 'aliases' do 85 | subject { "aaa".method(:+) } 86 | let(:x) { "foo" } 87 | end 88 | 89 | it_should_behave_like 'aliases(varargs)' do 90 | subject { "foobarbaz".method(:delete) } 91 | let(:x) { 'r' } 92 | let(:y) { 'o' } 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/op_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | shared_examples_for 'method_or_lambda' do |label| 5 | let(:method_name) { :+ } 6 | context 'given method name (is reciever object responds)' do 7 | subject { method } 8 | 9 | it { should be_a_kind_of Method } 10 | it("#{label}.call(args) should be obj.send(method_name, args)"){ 11 | subject.call('bar').should == object.send(method_name, 'bar') 12 | } 13 | end 14 | 15 | context 'given method name (is not reciever object responds)' do 16 | subject { lambda_proc } 17 | before { 18 | def object.method_missing(method, *args) 19 | method == :foo ? args : super(method, *args) 20 | end 21 | } 22 | 23 | it { should be_a_kind_of Proc } 24 | it("#{label}.call(args) should be obj.send(method_name, args)"){ 25 | lambda_proc.call('bar', 'baz').should == object.send(:foo, 'bar', 'baz') 26 | } 27 | end 28 | end 29 | 30 | describe LambdaDriver::Op do 31 | describe '#_' do 32 | let(:object) { "foobarbaz" } 33 | 34 | context 'given nil' do 35 | subject { object._ } 36 | 37 | it { subject.class.should be_a_kind_of LambdaDriver::Op::Proxy } 38 | end 39 | 40 | it_should_behave_like 'method_or_lambda', 'obj._(method_name)' do 41 | let(:method){ object._(:+) } 42 | let(:lambda_proc){ object._(:foo) } 43 | end 44 | end 45 | end 46 | 47 | describe LambdaDriver::Op::Proxy do 48 | let(:object) { "foobarbaz" } 49 | 50 | describe '#call' do 51 | it_should_behave_like 'method_or_lambda', 'obj._(method_name)' do 52 | let(:method){ object._.call(:+) } 53 | let(:lambda_proc){ object._.call(:foo) } 54 | end 55 | end 56 | 57 | describe '#method_missing' do 58 | it_should_behave_like 'method_or_lambda', 'obj._.method_name' do 59 | let(:method){ object._.index } 60 | let(:method_name) { :index } 61 | let(:lambda_proc){ object._.foo } 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/proc_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe Proc do 5 | describe '#compose' do 6 | subject { Proc.new{|x| x.to_s} } 7 | 8 | it_should_behave_like 'composable' 9 | end 10 | 11 | describe '#compose_with_lifting' do 12 | subject { Proc.new{|x| x.mzero? ? x : x.to_s} } 13 | 14 | it_should_behave_like 'liftable' 15 | end 16 | 17 | describe '#with_args' do 18 | subject { Proc.new{|x, y, *z| [x, y] + z.to_a } } 19 | it_should_behave_like 'with_args' 20 | end 21 | 22 | describe '#flip' do 23 | context 'arity = 1' do 24 | subject { Proc.new{|x| [x] } } 25 | 26 | it_should_behave_like 'flip(arity=1)' 27 | end 28 | 29 | context 'arity > 1' do 30 | subject { Proc.new{|x, y| [x, y] } } 31 | 32 | it_should_behave_like 'flip' 33 | end 34 | 35 | context 'varargs' do 36 | subject { Proc.new{|*x| x } } 37 | 38 | it_should_behave_like 'flip(varargs)' 39 | end 40 | end 41 | 42 | describe '#curry' do 43 | subject { Proc.new{|x, y| [x, y] } } 44 | 45 | it_should_behave_like 'curry' 46 | 47 | context 'varargs' do 48 | subject { Proc.new{|*x| x } } 49 | 50 | it_should_behave_like 'curry(varargs)' 51 | end 52 | end 53 | 54 | describe '#call' do 55 | subject { Proc.new{|x| x.to_s + "_f"} } 56 | 57 | it_should_behave_like 'call' 58 | it_should_behave_like 'call(<)' 59 | end 60 | 61 | describe 'ailases' do 62 | subject { Proc.new{|x| x.to_s + "_f"} } 63 | 64 | it_should_behave_like 'aliases' 65 | it_should_behave_like 'aliases(varargs)' do 66 | subject { Proc.new{|*x| x } } 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/revapply_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe LambdaDriver::Revapply do 5 | let(:object) { "foobarbaz" } 6 | let(:f) { lambda{|x| x * 2 } } 7 | 8 | describe '#revapply'do 9 | context 'given none' do 10 | subject { object.revapply } 11 | 12 | it { should be_a_kind_of Method } 13 | it('obj.revapply.call(f) should be f.call(obj)'){ 14 | subject.call(f).should == f.call(object) 15 | } 16 | it('obj.revapply.call{block} should be result of execute block with obj'){ 17 | subject.call{|x| x * 3 }.should == (object * 3) 18 | } 19 | it('obj.revapply.call(f){block} should be result of execute block with obj'){ 20 | subject.call(f){|x| x * 3 }.should == (object * 3) 21 | } 22 | end 23 | 24 | context 'block given' do 25 | it('obj.revapply{block} should be result of execute block with obj'){ 26 | object.revapply{|x| x * 3 }.should == (object * 3) 27 | } 28 | end 29 | 30 | context 'proc object given' do 31 | it('obj.revapply(f) should be f.call(obj)'){ 32 | object.revapply(f).should == f.call(object) 33 | } 34 | end 35 | 36 | context 'block given and proc object given' do 37 | it('obj.revapply.call(f){block} should be result of execute block with obj'){ 38 | object.revapply(f){|x| x * 3 }.should == (object * 3) 39 | } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/shared/composable_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | shared_examples_for 'composable' do 5 | it { should respond_to :to_proc} 6 | it { should respond_to :>> } 7 | it { should respond_to :<< } 8 | it { should respond_to :compose } 9 | 10 | let(:x) { :foo } 11 | let(:y) { 12 } 12 | let(:g) { lambda{|x| (x.to_s * 2) + "_g" } } 13 | 14 | it('" f >> g" returns g(f)'){ 15 | (subject >> g).should be_a_kind_of Proc 16 | } 17 | it('"(f >> g).call(x)" should be g(f(x))'){ 18 | (subject >> g).call(x).should == g.call(subject.to_proc.call(x)) 19 | } 20 | 21 | it('" f << g" returns g(f)'){ 22 | (subject << g).should be_a_kind_of Proc 23 | } 24 | it('"(f << g).call(x)" should be f(g(x))'){ 25 | (subject << g).call(y).should == subject.to_proc.call(g.call(y)) } 26 | end 27 | 28 | shared_examples_for 'with_args' do 29 | it { should respond_to :with_args } 30 | it { should respond_to :*} 31 | 32 | let(:x) { :foo } 33 | let(:y) { :bar } 34 | let(:z) { :baz } 35 | 36 | it('" f.with_args(x) returns proc which is partaily appied arguments'){ 37 | subject.with_args(y).should be_a_kind_of Proc 38 | } 39 | 40 | it('" f.with_args(y).call(x)" should be f(x, y)'){ 41 | puts x.inspect 42 | subject.with_args(y).call(x).should == subject.to_proc.call(x, y) 43 | } 44 | 45 | it('" f.with_args(*args).call(x)" should be f(x, *args)'){ 46 | subject.with_args(y, z).call(x).should == subject.to_proc.call(x, y, z) 47 | } 48 | 49 | it('" f * x returns proc which is partaily appied arguments'){ 50 | (subject * y).should be_a_kind_of Proc 51 | } 52 | 53 | it('" (f * y).call(x)" should be f(x, y)'){ 54 | (subject * y).call(x).should == subject.to_proc.call(x, y) 55 | } 56 | end 57 | 58 | shared_examples_for 'flip' do 59 | it { should respond_to :flip } 60 | it { should respond_to :~} 61 | 62 | let(:x) { :foo } 63 | let(:y) { :bar } 64 | 65 | it(' f.flip returns function whose parameter order swaped 1st for 2nd.'){ 66 | subject.flip.should be_a_kind_of Proc 67 | } 68 | 69 | it(' f.flip.call(x) returns proc ') { 70 | subject.flip.call(y).should be_a_kind_of Proc 71 | } 72 | it(' f.flip.call(x).call(y) should be (y,x)'){ 73 | subject.flip.call(y).call(x).should == subject.to_proc.call(x, y) 74 | } 75 | 76 | it(' ~f returns function whose parameter order swaped 1st for 2nd.'){ 77 | (~subject).should be_a_kind_of Proc 78 | } 79 | 80 | it(' ~f.call(x) returns proc ') { 81 | (~subject).call(y).should be_a_kind_of Proc 82 | } 83 | it(' ~f.call(x).call(y) should be (y,x)'){ 84 | (~subject).call(y).call(x).should == subject.to_proc.call(x, y) 85 | } 86 | end 87 | 88 | shared_examples_for 'flip(arity=1)' do 89 | let(:x) { :foo } 90 | let(:y) { :bar } 91 | 92 | it(' f.flip returns itself'){ 93 | subject.flip.should == subject 94 | } 95 | 96 | it(' f.flip.call(x) returns proc ') { 97 | subject.flip.to_proc.call(y) == subject.to_proc.call(y) 98 | } 99 | end 100 | 101 | shared_examples_for 'flip(varargs)' do 102 | let(:x) { :foo } 103 | let(:y) { :bar } 104 | 105 | it('f.flip returns itself'){ 106 | subject.flip.should == subject 107 | } 108 | 109 | it('f.flip(2).call(x) returns proc ') { 110 | subject.flip(2).call(y) == subject.to_proc.call(y) 111 | } 112 | 113 | it('~f.flip(2).call(x).call(y) should be f.call(y,x)'){ 114 | subject.flip(2).call(y).call(x).should == subject.to_proc.call(x, y) 115 | } 116 | end 117 | 118 | shared_examples_for 'curry' do 119 | it { should respond_to :curry} 120 | it { should respond_to :%} 121 | 122 | let(:x) { :foo } 123 | let(:y) { :bar } 124 | 125 | it('f.curry returns curried function'){ 126 | subject.curry.should be_a_kind_of Proc 127 | } 128 | 129 | it('f.curry.call(x) returns proc ') { 130 | subject.curry.call(y).should be_a_kind_of Proc 131 | } 132 | it('f.curry.call(x).call(y) should be f.call(x,y)'){ 133 | subject.curry.call(x).call(y).should == subject.to_proc.call(x, y) 134 | } 135 | end 136 | 137 | shared_examples_for 'curry(varargs)' do 138 | let(:x) { :foo } 139 | let(:y) { :bar } 140 | 141 | it('f % 2 returns curried function'){ 142 | (subject % 2).should be_a_kind_of Proc 143 | } 144 | 145 | it('(f % 2).call(x) returns proc ') { 146 | (subject % 2).call(y).should be_a_kind_of Proc 147 | } 148 | it('(f % 2).call(x).call(y) should be f.call(x,y)'){ 149 | (subject % 2).call(x).call(y).should == subject.to_proc.call(x, y) 150 | } 151 | end 152 | 153 | shared_examples_for 'call' do 154 | it { should respond_to :call} 155 | 156 | let(:x) { :foo } 157 | 158 | it('f.call(x) == f.call(x)'){ 159 | subject.call(x).should == subject.call(x) 160 | } 161 | end 162 | 163 | shared_examples_for 'call(<)' do 164 | it { should respond_to :<} 165 | 166 | let(:x) { :foo } 167 | 168 | it('(f < x) == f.call(x)'){ 169 | (subject < x).should == subject.to_proc.call(x) 170 | } 171 | end 172 | 173 | shared_examples_for 'aliases' do 174 | let(:x) { :foo } 175 | let(:y) { 12 } 176 | let(:g) { lambda{|x| (x.to_s * 2).to_s + "_g" } } 177 | 178 | it('(~f < x) should be f.flip.call(x)'){ 179 | (~subject < x).should == subject.flip.to_proc.call(x) 180 | } 181 | 182 | it('(f << g < x) should f.compose(g).call(x)'){ 183 | (subject << g < x).should == subject.compose(g).call(x) 184 | } 185 | 186 | it('(f >> g < x) should g.compose(f).call(x)'){ 187 | (subject >> g < x).should == g.compose(subject).call(x) 188 | } 189 | end 190 | 191 | # require 'lambda_driver' 192 | # x, y, g = [:foo, 12, lambda{|x| (x.to_s * 2).to_s + "_g" }] 193 | # subject = Proc.new{|*x| x] 194 | 195 | shared_examples_for 'aliases(varargs)' do 196 | let(:x) { :foo } 197 | let(:y) { 12 } 198 | let(:g) { lambda{|x| (x.to_s * 2).to_s + "_g" } } 199 | 200 | it('(f % 2 * y << g < x) should be f.curry(2).compose(g).call(x)'){ 201 | ((subject % 2 < y) << g < x).should == subject.curry(2).call(y).compose(g).call(x) 202 | } 203 | 204 | it('(f.flip(2) * y << g < x) should be f.flip(2).compose(g).call(x)'){ 205 | ((subject.flip(2) < y) << g < x).should == subject.flip(2).call(y).compose(g).call(x) 206 | } 207 | end 208 | -------------------------------------------------------------------------------- /spec/shared/liftable.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | shared_examples_for 'liftable' do 5 | it { should respond_to :compose_with_lifting } 6 | it { should respond_to :>= } 7 | it { should respond_to :<= } 8 | it { should respond_to :ymsr} 9 | 10 | let(:x) { :foo } 11 | let(:y) { [1,2] } 12 | let(:z) { {:a => 1, :b => 2} } 13 | let(:g) { lambda{|x| 14 | if x.nil? || (x.respond_to?(:empty?) && x.empty?) 15 | raise "g called with nil arg" 16 | else 17 | (x.to_s * 2) + "_g" 18 | end 19 | } 20 | } 21 | let(:h) { lambda{|x| (x.to_s * 3) + "_h" } } 22 | 23 | context 'lift on default-context' do 24 | it('" f >= g" returns function that does not call g if args is mzero'){ 25 | (subject >= g).should be_a_kind_of Proc 26 | } 27 | 28 | it('"(f >= g).call(nil) returns nil and does not call g'){ 29 | (subject >= g).call(nil).should be_nil 30 | } 31 | it('"(f >= g).call(x) should be g(f(x))'){ 32 | (subject >= g).call(x).should == g.call(subject.to_proc.call(x)) 33 | } 34 | it('"(f >= g).call([]) returns [] and does not call g'){ 35 | (subject >= g).call([]).should == [] 36 | } 37 | it('"(f >= g).call([1,2]) should be g(f([1,2]))'){ 38 | (subject >= g).call(y).should == g.call(subject.to_proc.call(y)) 39 | } 40 | it('"(f >= g).call({}) returns {} and does not call g'){ 41 | (subject >= g).call({}).should == {} 42 | } 43 | it('"(f >= g).call({:a => 1,:b => 2}) should be g(f({:a => 1, :b => 2}))'){ 44 | (subject >= g).call(z).should == g.call(subject.to_proc.call(z)) 45 | } 46 | end 47 | 48 | context 'lift on given context' do 49 | let(:ctx) { lambda{|f,x| f.call(x) + "_ctx_" }} 50 | 51 | it(" f.lift(ctx) >= g returns funciton that compose with ctx"){ 52 | (subject.lift(ctx) >= g).should be_a_kind_of Proc 53 | } 54 | it('"(f.lift(ctx) >= g).call(x) should be ctx(g, ctx(f, x))'){ 55 | (subject.lift(ctx) >= g).call(x).should == ctx.call(g, ctx.call(subject.to_proc,x)) 56 | } 57 | it('"(f.lift(ctx) >= g >= h).call(x) should be ctx(h, ctx(g, f(x)))'){ 58 | (subject.lift(ctx) >= g >= h).call(x).should == ctx.call(h, ctx.call(g, ctx.call(subject.to_proc, x))) 59 | } 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | # This file was generated by the `rspec --init` command. Conventionally, all 3 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 4 | # Require this file using `require "spec_helper.rb"` to ensure that it is only 5 | # loaded once. 6 | # 7 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 8 | # 9 | require 'rspec' 10 | require 'lambda_driver' 11 | 12 | Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each {|f| require f} 13 | 14 | RSpec.configure do |config| 15 | config.run_all_when_everything_filtered = true 16 | config.filter_run :focus 17 | config.expect_with(:rspec) { |c| c.syntax = :should } 18 | end 19 | -------------------------------------------------------------------------------- /spec/symbol_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding : utf-8 -*- 2 | require 'spec_helper' 3 | 4 | describe Symbol do 5 | describe '#compose' do 6 | subject { :to_s } 7 | 8 | it_should_behave_like 'composable' 9 | end 10 | 11 | describe '#with_args' do 12 | subject {:delete } 13 | 14 | it_should_behave_like 'with_args' do 15 | let(:x) { 'foobar' } 16 | let(:y) { 'o' } 17 | let(:z) { 'a'} 18 | end 19 | end 20 | 21 | describe '#flip' do 22 | context 'arity = 1' do 23 | subject { :to_s } 24 | 25 | it_should_behave_like 'flip(arity=1)' 26 | end 27 | 28 | context 'varargs' do 29 | subject {:product } 30 | 31 | it_should_behave_like 'flip(varargs)' do 32 | let(:x) { [:bar] } 33 | let(:y) { [:foo] } 34 | end 35 | end 36 | end 37 | 38 | describe '#curry' do 39 | subject { :product } 40 | 41 | it_should_behave_like 'curry(varargs)' do 42 | let(:x) { [:bar] } 43 | let(:y) { [:foo] } 44 | end 45 | end 46 | 47 | describe '#call' do 48 | subject { :to_s } 49 | 50 | it_should_behave_like 'call(<)' 51 | end 52 | 53 | describe 'ailases' do 54 | subject { :to_s } 55 | 56 | it_should_behave_like 'aliases' 57 | 58 | it_should_behave_like 'aliases(varargs)' do 59 | subject { :delete} 60 | let(:x) { :bar } 61 | let(:y) { 'a' } 62 | end 63 | end 64 | 65 | describe '#to_method' do 66 | subject { :index.to_method } 67 | 68 | let(:obj) { "foobarbaz" } 69 | 70 | it { should be_a_kind_of Proc } 71 | it('symbol.to_method.call(obj) should returns Method'){ 72 | subject.call(obj).should be_a_kind_of Method 73 | } 74 | 75 | it('symbol.to_method.call(obj).call(x) should be obj.method(symbol).call(x)'){ 76 | subject.call(obj).call("bar").should == obj.method(:index).call("bar") 77 | } 78 | 79 | it('(-:symbol).call(obj) should returns Method'){ 80 | (-:index).call(obj).should be_a_kind_of Method 81 | } 82 | 83 | it('(-:symbol).call(obj).call(x) should be obj.method(symbol).call(x)'){ 84 | (-:index).call(obj).call("bar").should == obj.method(:index).call("bar") 85 | } 86 | 87 | it('-:symbol < obj < x should be obj.method(symbol).call(x)'){ 88 | (-:index < obj <"bar").should == obj.method(:index).call("bar") 89 | } 90 | end 91 | 92 | describe '#to_method_with_args' do 93 | subject { :index.to_method_with_args("bar") } 94 | 95 | let(:obj) { "foobarbaz" } 96 | 97 | it { should be_a_kind_of Proc } 98 | 99 | it('symbol.to_method_with_args(x).call(obj) should be obj.method(symbol).call(x)'){ 100 | subject.call(obj).should == obj.method(:index).call("bar") 101 | } 102 | 103 | it('(:symbol & x).call(obj) should be obj.method(symbol).call(x)'){ 104 | (:index & 'bar').call(obj).should == obj.method(:index).call("bar") 105 | } 106 | 107 | it('(:symbol & x