├── .gemspec ├── .gitignore ├── .index ├── .travis.yml ├── .yardopts ├── Assembly ├── Gemfile ├── HISOTRY.md ├── Indexfile ├── LICENSE.txt ├── MANIFEST ├── README.md ├── lib ├── functor.rb └── functor │ ├── array │ └── recursively.rb │ ├── enumerable │ ├── accrue.rb │ ├── accumulate.rb │ ├── all.rb │ ├── any.rb │ ├── apply.rb │ ├── every.rb │ ├── ewise.rb │ ├── having.rb │ ├── in_order_of.rb │ ├── per.rb │ ├── recursively.rb │ └── where.rb │ ├── enumerator │ └── fx.rb │ ├── extensions.rb │ ├── hash │ ├── data.rb │ └── recursively.rb │ ├── kernel │ ├── as.rb │ ├── as_new.rb │ ├── eigen.rb │ ├── ergo.rb │ ├── meta.rb │ ├── not.rb │ ├── respond.rb │ ├── tap.rb │ └── try.rb │ ├── module │ └── method_space.rb │ ├── object │ └── against.rb │ ├── object_space │ └── reflect.rb │ ├── string │ ├── file.rb │ └── linear.rb │ ├── symbol │ └── as_s.rb │ └── version.rb └── test └── case_functor.rb /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # For which revision of .ruby is this gemspec intended? 11 | REVISION = 0 unless defined?(REVISION) 12 | 13 | # 14 | PATTERNS = { 15 | :bin_files => 'bin/*', 16 | :lib_files => 'lib/{**/}*.rb', 17 | :ext_files => 'ext/{**/}extconf.rb', 18 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 19 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 20 | } unless defined?(PATTERNS) 21 | 22 | # 23 | def self.instance 24 | new.to_gemspec 25 | end 26 | 27 | attr :metadata 28 | 29 | attr :manifest 30 | 31 | # 32 | def initialize 33 | @metadata = YAML.load_file('.ruby') 34 | @manifest = Dir.glob('manifest{,.txt}', File::FNM_CASEFOLD).first 35 | 36 | if @metadata['revision'].to_i != REVISION 37 | warn "You have the wrong revision. Trying anyway..." 38 | end 39 | end 40 | 41 | # 42 | def scm 43 | @scm ||= \ 44 | case 45 | when File.directory?('.git') 46 | :git 47 | end 48 | end 49 | 50 | # 51 | def files 52 | @files ||= \ 53 | #glob_files[patterns[:files]] 54 | case 55 | when manifest 56 | File.readlines(manifest). 57 | map{ |line| line.strip }. 58 | reject{ |line| line.empty? || line[0,1] == '#' } 59 | when scm == :git 60 | `git ls-files -z`.split("\0") 61 | else 62 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 63 | end.select{ |path| File.file?(path) } 64 | end 65 | 66 | # 67 | def glob_files(pattern) 68 | Dir.glob(pattern).select { |path| 69 | File.file?(path) && files.include?(path) 70 | } 71 | end 72 | 73 | # 74 | def patterns 75 | PATTERNS 76 | end 77 | 78 | # 79 | def executables 80 | @executables ||= \ 81 | glob_files(patterns[:bin_files]).map do |path| 82 | File.basename(path) 83 | end 84 | end 85 | 86 | def extensions 87 | @extensions ||= \ 88 | glob_files(patterns[:ext_files]).map do |path| 89 | File.basename(path) 90 | end 91 | end 92 | 93 | # 94 | def name 95 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 96 | end 97 | 98 | # 99 | def to_gemspec 100 | Gem::Specification.new do |gemspec| 101 | gemspec.name = name 102 | gemspec.version = metadata['version'] 103 | gemspec.summary = metadata['summary'] 104 | gemspec.description = metadata['description'] 105 | 106 | metadata['authors'].each do |author| 107 | gemspec.authors << author['name'] 108 | 109 | if author.has_key?('email') 110 | if gemspec.email 111 | gemspec.email << author['email'] 112 | else 113 | gemspec.email = [author['email']] 114 | end 115 | end 116 | end 117 | 118 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 119 | 120 | metadata['requirements'].each do |req| 121 | name = req['name'] 122 | version = req['version'] 123 | groups = req['groups'] || [] 124 | 125 | case version 126 | when /^(.*?)\+$/ 127 | version = ">= #{$1}" 128 | when /^(.*?)\-$/ 129 | version = "< #{$1}" 130 | when /^(.*?)\~$/ 131 | version = "~> #{$1}" 132 | end 133 | 134 | if groups.empty? or groups.include?('runtime') 135 | # populate runtime dependencies 136 | if gemspec.respond_to?(:add_runtime_dependency) 137 | gemspec.add_runtime_dependency(name,*version) 138 | else 139 | gemspec.add_dependency(name,*version) 140 | end 141 | else 142 | # populate development dependencies 143 | if gemspec.respond_to?(:add_development_dependency) 144 | gemspec.add_development_dependency(name,*version) 145 | else 146 | gemspec.add_dependency(name,*version) 147 | end 148 | end 149 | end 150 | 151 | # convert external dependencies into a requirements 152 | if metadata['external_dependencies'] 153 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 154 | metadata['external_dependencies'].each do |req| 155 | gemspec.requirements << req.to_s 156 | end 157 | end 158 | 159 | # determine homepage from resources 160 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 161 | gemspec.homepage = homepage.last if homepage 162 | 163 | gemspec.require_paths = metadata['load_path'] || ['lib'] 164 | gemspec.post_install_message = metadata['install_message'] 165 | 166 | # RubyGems specific metadata 167 | gemspec.files = files 168 | gemspec.extensions = extensions 169 | gemspec.executables = executables 170 | 171 | if Gem::VERSION < '1.7.' 172 | gemspec.default_executable = gemspec.executables.first 173 | end 174 | 175 | gemspec.test_files = glob_files(patterns[:test_files]) 176 | 177 | unless gemspec.files.include?('.document') 178 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 179 | end 180 | end 181 | end 182 | 183 | end #class GemSpec 184 | 185 | end 186 | 187 | DotRuby::GemSpec.instance 188 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | log 4 | pkg 5 | tmp 6 | web 7 | work 8 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Indexfile 6 | authors: 7 | - name: Thomas Sawyer 8 | email: transfire@gmail.com 9 | organizations: [] 10 | requirements: 11 | - groups: 12 | - test 13 | development: true 14 | name: lemon 15 | - groups: 16 | - test 17 | development: true 18 | name: ae 19 | conflicts: [] 20 | alternatives: [] 21 | resources: 22 | - type: home 23 | uri: http://rubyworks.github.com/functor 24 | label: Homepage 25 | - type: code 26 | uri: http://github.com/rubyworks/functor 27 | label: Source Code 28 | repositories: 29 | - name: upstream 30 | scm: git 31 | uri: git://github.com/rubyworks/functor.git 32 | categories: [] 33 | copyrights: 34 | - holder: Rubyworks 35 | year: '2011' 36 | license: BSD-2 37 | customs: [] 38 | paths: 39 | lib: 40 | - lib 41 | name: functor 42 | title: Functor 43 | version: 1.0.0 44 | summary: Higher-Order Methods for Ruby 45 | description: By definition a Functor is simply a first class method, but these are 46 | common in the form of Method and Proc. So for Ruby a Functor is a more specialized 47 | as a Higher-order function or Metafunction. Essentally, a Functor can vary its behavior 48 | accorrding to the operation applied to it. 49 | created: '2004-03-04' 50 | date: '2015-02-23' 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec ruby-test -Ilib -rlemon test/" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - rbx-2.0 8 | - jruby 9 | - ree 10 | 11 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "OpenDSL" 2 | --readme README.rdoc 3 | --protected 4 | --private 5 | lib/**/*.rb 6 | - 7 | [A-Z]*.* 8 | 9 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | erbside: 3 | path: lib/functor/version.rb 4 | 5 | email: 6 | mailto: 7 | - ruby-talk@ruby-lang.org 8 | - rubyworks-mailinglist@googlegroups.com 9 | 10 | qed: 11 | active: false 12 | files: qed/ 13 | 14 | qedoc: 15 | active: false 16 | files: qed/ 17 | title: "OpenDSL Demonstandum" 18 | output: QED.rdoc 19 | 20 | gem: 21 | active: true 22 | 23 | github: 24 | gh_pages: web 25 | 26 | dnote: 27 | title: Source Notes 28 | output: log/NOTES.rdoc 29 | 30 | vclog: 31 | output: 32 | - log/History.rdoc 33 | - log/Changes.rdoc 34 | 35 | tag: 36 | service: custom 37 | release: | 38 | puts %[pom news | git tag -a -F - #{project.version}] 39 | 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /HISOTRY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 1.0.0 | 2011-12-07 4 | 5 | Functor is a spin-off project form Ruby Facets. It provides Ruby with 6 | higher-order functions via the Functor class. 7 | 8 | Changes: 9 | 10 | * Initial independent release. 11 | -------------------------------------------------------------------------------- /Indexfile: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | functor 4 | 5 | version: 6 | 1.0.0 7 | 8 | title: 9 | Functor 10 | 11 | summary: 12 | Higher-Order Methods for Ruby 13 | 14 | description: 15 | By definition a Functor is simply a first class method, but these are common 16 | in the form of Method and Proc. So for Ruby a Functor is a more specialized 17 | as a Higher-order function or Metafunction. Essentally, a Functor can vary 18 | its behavior accorrding to the operation applied to it. 19 | 20 | requirements: 21 | #- detroit (build) 22 | - lemon (test) 23 | - ae (test) 24 | 25 | resources: 26 | home: http://rubyworks.github.com/functor 27 | code: http://github.com/rubyworks/functor 28 | 29 | repositories: 30 | upstream: git://github.com/rubyworks/functor.git 31 | 32 | authors: 33 | - Thomas Sawyer 34 | 35 | created: 36 | 2004-03-04 37 | 38 | copyrights: 39 | - 2011 Rubyworks (BSD-2) 40 | 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yardopts bin lib man qed spec test [A-Z]*.* 2 | .ruby 3 | .yardopts 4 | lib/functor/version.rb 5 | lib/functor.rb 6 | test/case_functor.rb 7 | HISOTRY.md 8 | LICENSE.txt 9 | README.md 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functor 2 | 3 | [Homepage](http://rubyworks.github.com/functor) | 4 | [Source Code](http://github.com/rubyworks/functor) | 5 | [Report an Issue](http://github.com/rubyworks/functor/issues) 6 | 7 | 8 | ## Synopsis 9 | 10 | By definition a *Functor* is simply a first class method, but these are quite 11 | common in Ruby with the `Method`, `UnboundMethod` and `Proc` classes. So for 12 | Ruby we define a Functor as a *Higher Order Function*. Essentially, a functor 13 | can vary its behavior according to the operation applied to it. Consider the 14 | following simplistic example. 15 | 16 | f = Functor.new { |op, x| x.send(op, x) } 17 | 18 | f + 1 #=> 2 19 | f + 2 #=> 4 20 | f + 3 #=> 6 21 | f * 1 #=> 1 22 | f * 2 #=> 4 23 | f * 3 #=> 9 24 | 25 | Notice that the constructor takes a block, the first argument of which is always 26 | the method operating on the functor. All subsequent arguments are optional 27 | dependent upon the use case. 28 | 29 | We can also think of the Functor as an *anonymous/generic delegator*. Instead 30 | of having to create a specialized delegator class, a functor can be used instead. 31 | Functors are perfect when the delegation required is minimal. 32 | 33 | **NOTE** Ruby functors are not the same as Haskell functors. In Ruby 34 | Enumerators are more akin to Haskell's idea of a functor. 35 | 36 | 37 | ## Batteries Included 38 | 39 | In addition to the Functor class, this library includes a dozen or so useful 40 | core extensions that take advantage of the power of the functor. Have a look 41 | at the demo directory for a rundown on the methods available and how they 42 | work. 43 | 44 | 45 | ## Copyrights 46 | 47 | Copyright (c) 2004 Rubyworks 48 | 49 | Functor is distributable in accordance with the *BSD-2-Clause* license. 50 | 51 | See LICENSE.txt for details. 52 | 53 | -------------------------------------------------------------------------------- /lib/functor.rb: -------------------------------------------------------------------------------- 1 | # The Ruby Functor is an Higher Order Message. Essentally, 2 | # it is anonymous delegator, an object that can vary its 3 | # behavior according to the operations applied to it. 4 | # 5 | # f = Functor.new { |op, x| x.send(op, x) } 6 | # (f + 1) #=> 2 7 | # (f + 2) #=> 4 8 | # (f + 3) #=> 6 9 | # (f * 1) #=> 1 10 | # (f * 2) #=> 4 11 | # (f * 3) #=> 9 12 | # 13 | class Functor < BasicObject 14 | 15 | EXCEPTIONS = [:binding, :inspect, :object_id] 16 | 17 | if defined?(::BasicObject) 18 | EXCEPTIONS.concat(::BasicObject.instance_methods) 19 | EXCEPTIONS.uniq! 20 | EXCEPTIONS.map!{ |m| m.to_sym } 21 | end 22 | 23 | # 24 | alias :__class__ :class 25 | 26 | ## If Functor were built-in to Ruby this would not be 27 | ## needed since exceetions could just be added directly. 28 | ##$FUNCTOR_EXCEPTIONS ||= [:binding, :undefine_method] 29 | 30 | ## TODO: This will not work when BasicObject is utilized. How to fix? 31 | ##def self.honor_exceptions 32 | ## $FUNCTOR_EXCEPTIONS.each{ |name| 33 | ## next if method_defined?(name) 34 | ## eval %{ 35 | ## def #{name}(*a,&b) 36 | ## super(*a, &b) 37 | ## end 38 | ## } 39 | ## } 40 | ##end 41 | 42 | # Privatize all methods except vital methods and #binding. 43 | instance_methods(true).each do |m| 44 | next if m.to_s =~ /^__/ 45 | next if EXCEPTIONS.include?(m.to_sym) 46 | undef_method(m) 47 | end 48 | 49 | # 50 | def initialize(&function) 51 | @function = function 52 | end 53 | 54 | # 55 | def to_proc 56 | @function 57 | end 58 | 59 | ##def inspect 60 | ## #"#" # hex id ? 61 | ## "#{method_missing(:inspect)}" 62 | ##end 63 | 64 | ## Needed? 65 | ##def send(op, *a, &b) 66 | ## method_missing(op, *a, &b) 67 | ##end 68 | 69 | private 70 | 71 | # Any action against the functor is processesd by the function. 72 | def method_missing(op, *args, &blk) 73 | @function.call(op, *args, &blk) 74 | end 75 | 76 | end 77 | 78 | # Copyright (c) 2004 Rubyworks/Thomas Sawyer 79 | -------------------------------------------------------------------------------- /lib/functor/array/recursively.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | require 'functor/enumerable/recursively' 3 | 4 | class Array 5 | 6 | # Apply a method to array, and recursively apply that method 7 | # to each sub-array or given +types+. 8 | # 9 | # By default the sub-types are passed thru uneffected. Passing 10 | # a block to #recursively can be used to change this. 11 | # 12 | # types - List of class types to recurse. [Array] 13 | # block - Optional filter procedure to apply on each recursion. 14 | # 15 | # Examples 16 | # 17 | # arr = ["a", ["b", "c"]] 18 | # arr.recursively.map{ |v| v.to_sym } 19 | # #=> [:a, [:b, :c]] 20 | # 21 | # arr = ["a", ["b", "c"]] 22 | # arr.recursively{ |a| a.reverse }.map{ |v| v.to_sym } 23 | # #=> [:a, [:c, :b]] 24 | # 25 | # Returns [Recursor]. 26 | 27 | def recursively(*types, &block) 28 | Recursor.new(self, *types, &block) 29 | end 30 | 31 | ## TODO: When no longer needing to support 1.8.6 we could get rid of 32 | ## the Recursor class and use: 33 | ## 34 | ## def recursively(*types, &block) 35 | ## types = types.empty? ? [self.class] : types 36 | ## Functor.new do |op, &yld| 37 | ## rec = block || lambda{ |a| a } 38 | ## yld = yld || lambda{ |v| v } # ? to_enum 39 | ## __send__(op) do |v| 40 | ## case v 41 | ## when String # b/c of 1.8 42 | ## yld.call(v) 43 | ## when *types 44 | ## res = v.recursively(*types, &block).__send__(op,&yld) 45 | ## rec.call(res) 46 | ## else 47 | ## yld.call(v) 48 | ## end 49 | ## end 50 | ## end 51 | ## end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /lib/functor/enumerable/accrue.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # An higher order method akin to `#inject`. 6 | # 7 | # [1,2,3].accrue.+ #=> 6 8 | # 9 | def accrue 10 | Functor.new do |op, *a, &b| 11 | inject do |c, e| 12 | c.public_send(op, e, *a, &b) 13 | end 14 | end 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/functor/enumerable/accumulate.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Accumulate a set of a set. For example, in an ORM design 6 | # where `Group has_many User` we might have something 7 | # equivalent to the following. 8 | # 9 | # Group = Struct.new(:users) 10 | # User = Struct.new(:name, :friends) 11 | # 12 | # user1 = User.new('John', []) 13 | # user2 = User.new('Jane', ['Jill']) 14 | # user3 = User.new('Joe' , ['Jack', 'Jim']) 15 | # 16 | # group1 = Group.new([user1, user2]) 17 | # group2 = Group.new([user2, user3]) 18 | # 19 | # groups = [group1, group2] 20 | # 21 | # Now we can *accumulate* the users of all groups. 22 | # 23 | # groups.accumulate.users #=> [user1, user2, user3] 24 | # 25 | # You may pass an argument to perform chains, e.g. the following 26 | # returns the names of users from all groups. 27 | # 28 | # groups.accumulate(2).users.name #=> ['John','Jane','Joe'] 29 | # 30 | # Or we can gather all the friends of all users in groups. 31 | # 32 | # groups.accumulate(2).users.friends #=> ['Jill','Jack','Jim'] 33 | # 34 | # This is more convenient then the equivalent. 35 | # 36 | # groups.accumulate.users.accumulate.friends #=> ['Jill','Jack','Jim'] 37 | # 38 | # TODO: Not 100% liking the name of Enumerable#accumulate. Any better ideas? 39 | # 40 | # CREDIT: George Moshchovitis, Daniel Emirikol 41 | 42 | def accumulate(iterations=1) 43 | return self if iterations == 0 44 | Functor.new do |op, *args| 45 | #result = inject([]) { |a, x| a << x.send(op, *args) }.flatten.uniq 46 | result = [] 47 | each { |x| result << x.send(op, *args) } 48 | result.flatten.uniq.accumulate(iterations - 1) 49 | end 50 | end 51 | 52 | # Same as #accumulate, but does not apply #uniq to final result. 53 | # 54 | # groups.accumulate_all(2).users.friends #=> ['Jill', 'Jill','Jack','Jim'] 55 | # 56 | def accumulate_all(iterations=1) 57 | return self if iterations == 0 58 | Functor.new do |op, *args| 59 | #result = inject([]) { |a, x| a << x.send(op, *args) }.flatten 60 | result = [] 61 | each { |x| result << x.send(op, *args) } 62 | result.flatten.accumulate_all(iterations - 1) 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /lib/functor/enumerable/all.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Like `#all?` but higher-order. 6 | # 7 | # [1,2,3,4].all > 2 #=> false 8 | # 9 | def all 10 | Functor.new do |op, *a, &b| 11 | all?{ |e| e.public_send(op, *a, &b) } 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/functor/enumerable/any.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Like `#any?` but higher-order. 6 | # 7 | # [1,2,3,4].any > 2 #=> true 8 | # 9 | def any 10 | Functor.new do |op, *a, &b| 11 | any?{ |e| e.public_send(op, *a, &b) } 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/functor/enumerable/apply.rb: -------------------------------------------------------------------------------- 1 | require 'functor/enumerable/per' 2 | 3 | module Enumerable 4 | 5 | # Returns an elemental object. This allows 6 | # you to map a method on to every element. 7 | # 8 | # r = [1,2,3].apply.+ 9 | # r #=> 6 10 | # 11 | def apply 12 | #Functor.new do |sym, *args, &blk| 13 | # inject{ |r, e| r.__send__(sym, e, *args, &blk) } 14 | #end 15 | per(:inject) 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /lib/functor/enumerable/every.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Higher-order form of `#map`. 6 | # 7 | # [1,2,3].every + 1 #=> [2,3,4] 8 | # 9 | def every 10 | Functor.new do |op, *a, &b| 11 | map do |e| 12 | e.public_send(op, *a, &b) 13 | end 14 | end 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/functor/enumerable/ewise.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Returns an elementwise HOM designed to make R-like elementwise 6 | # operations possible. This is very much like the #every method, 7 | # but it treats array argument specially. 8 | # 9 | # ([1,2].ewise + 3) #=> [4,5] 10 | # 11 | # Vector to vector 12 | # 13 | # ([1,2].ewise + [4,5]) #=> [5,7] 14 | # 15 | # Special thanks to Martin DeMello for helping to develop this. 16 | # 17 | # TODO: Maybe call this #r since it inspired by R? 18 | 19 | def ewise(count=1) 20 | Functor.new do |op,*args| 21 | if args.empty? 22 | r = self 23 | count.times do 24 | r = r.collect{ |a| a.send(op) } 25 | end 26 | r 27 | else 28 | r = args.collect do |arg| 29 | if Array === arg #arg.kind_of?(Enumerable) 30 | x = self 31 | count.times do 32 | ln = (arg.length > length ? length : arg.length ) 33 | x = x.slice(0...ln) 34 | x = x.zip(arg[0...ln]) 35 | x = x.collect{ |a,b| a.send(op,b) } #x = x.collect{ |a,b| b ? a.send(op,b) : nil } 36 | end 37 | x 38 | else 39 | x = self 40 | count.times do 41 | x = x.collect{ |a| a.send(op,arg) } 42 | end 43 | x 44 | end 45 | end 46 | r.flatten! if args.length == 1 47 | r 48 | end 49 | end 50 | end 51 | 52 | # Long-term for #ewise. 53 | # 54 | # a = [1,2] 55 | # (a.elementwise + 3) #=> [4,5] 56 | # (a.elementwise + [4,5]) #=> [5,7] 57 | # 58 | alias_method :elementwise, :ewise 59 | 60 | end 61 | -------------------------------------------------------------------------------- /lib/functor/enumerable/having.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Doubly higher-order form of `#select`, where the the first 6 | # call isolates an attribute of each element and the second 7 | # then handles the selection criteria. 8 | # 9 | # [1, 2, 3].having.age > 2 #=> [3] 10 | # 11 | def having 12 | Functor.new do |op1, *a1, &b1| 13 | Functor.new do |op2, *a2, &b2 14 | select{ |e| e.public_send(op1, *a1, &b1).public_send(op2, *a2, &b2) } 15 | end 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/functor/enumerable/in_order_of.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Put in order based on returned value of higher-order message. 6 | # 7 | # a = ["hello", "hi", "sure"] 8 | # a.in_order_of.size 9 | # #=> ["hi", "sure", "hello"] 10 | # 11 | def in_order_of 12 | Functor.new do |op, *a, &b| 13 | sort_by(op, *a, &b) 14 | end 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/functor/enumerable/per.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Per element meta-HOM. 6 | # 7 | # ([1,2,3].per(:map) + 3) #=> [4,5,6] 8 | # ([1,2,3].per(:select) > 1) #=> [2,3] 9 | # 10 | # Using fluid notation. 11 | # 12 | # ([1,2,3].per.map + 3) #=> [4,5,6] 13 | # ([1,2,3].per.select > 1) #=> [2,3] 14 | # 15 | def per(enum_method=nil, *enum_args) 16 | if enum_method 17 | Functor.new do |op, *args, &blk| 18 | __send__(enum_method, *enum_args){ |x| x.__send__(op, *args, &blk) } 19 | end 20 | else 21 | Functor.new do |enumr_method, *enumr_args| 22 | Functor.new do |op, *args, &blk| 23 | __send__(enumr_method, *enumr_args){ |x| x.__send__(op, *args, &blk) } 24 | end 25 | end 26 | end 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /lib/functor/enumerable/recursively.rb: -------------------------------------------------------------------------------- 1 | module Enumerable 2 | 3 | # Returns a recursive functor, that allows enumerable methods to iterate 4 | # through enumerable sub-elements. By default it only recurses over 5 | # elements of the same type. 6 | # 7 | def recursively(*types, &block) 8 | Recursor.new(self, *types, &block) 9 | end 10 | 11 | # Recursor is a specialized Functor for recurively iterating over Enumerables. 12 | # 13 | # TODO: Return Enumerator if no +yld+ block is given. 14 | # 15 | # TODO: Add limiting +depth+ option to Enumerable#recursively? 16 | # 17 | class Recursor 18 | instance_methods(true).each{ |m| private m unless /^(__|object_id$)/ =~ m.to_s } 19 | 20 | def initialize(enum, *types, &block) 21 | @enum = enum 22 | @types = types.empty? ? [@enum.class] : types 23 | @block = block 24 | end 25 | 26 | def method_missing(op, &yld) 27 | rec = @block || lambda{ |v| v } 28 | yld = yld || lambda{ |v| v } # ? to_enum 29 | @enum.__send__(op) do |v| 30 | case v 31 | when String # b/c of 1.8 32 | yld.call(v) 33 | when *@types 34 | res = v.recursively(*@types, &@block).__send__(op,&yld) 35 | rec.call(res) 36 | else 37 | yld.call(v) 38 | end 39 | end 40 | end 41 | end 42 | 43 | ## TODO: When no longer needing to support 1.8.6 we could get rid of 44 | ## the Recursor class and use: 45 | ## # 46 | ## def recursively(*types, &block) 47 | ## types = types.empty? ? [self.class] : types 48 | ## Functor.new do |op, &yld| 49 | ## rec = block || lambda{ |v| v } 50 | ## yld = yld || lambda{ |v| v } # ? to_enum 51 | ## __send__(op) do |v| 52 | ## case v 53 | ## when String # b/c of 1.8 54 | ## yld.call(v) 55 | ## when *types 56 | ## res = v.recursively(*types, &block).__send__(op,&yld) 57 | ## rec.call(res) 58 | ## else 59 | ## yld.call(v) 60 | ## end 61 | ## end 62 | ## end 63 | ## end 64 | 65 | end 66 | 67 | -------------------------------------------------------------------------------- /lib/functor/enumerable/where.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Enumerable 4 | 5 | # Higher-order form of #select. 6 | # 7 | # [1, 2, 3].where > 2 #=> [3] 8 | # 9 | def where 10 | Functor.new do |op, *a, &b| 11 | select{ |e| e.public_send(op, *a, &b) } 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lib/functor/enumerator/fx.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class Enumerator 4 | 5 | # Functorize Enumerator#map. This is the most like Haskell's idea of a Functor. 6 | # 7 | def fx 8 | Functor.new do |op, *a, &b| 9 | map{ |e| e.send(op, *a, &b) } 10 | end 11 | end 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/functor/extensions.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | require 'functor/array/recursively' 4 | 5 | require 'functor/enumerable/accrue' 6 | require 'functor/enumerable/accumulate' 7 | require 'functor/enumerable/all' 8 | require 'functor/enumerable/any' 9 | require 'functor/enumerable/apply' 10 | require 'functor/enumerable/every' 11 | require 'functor/enumerable/ewise' 12 | require 'functor/enumerable/having' 13 | require 'functor/enumerable/in_order_of' 14 | require 'functor/enumerable/per' 15 | require 'functor/enumerable/recursively' 16 | require 'functor/enumerable/where' 17 | 18 | require 'functor/enumerator/fx' 19 | 20 | require 'functor/hash/data' 21 | require 'functor/hash/recursively' 22 | 23 | require 'functor/kernel/as' 24 | require 'functor/kernel/as_new' 25 | require 'functor/kernel/eigen' 26 | require 'functor/kernel/ergo' 27 | require 'functor/kernel/meta' 28 | require 'functor/kernel/not' 29 | require 'functor/kernel/respond' 30 | require 'functor/kernel/tap' 31 | require 'functor/kernel/try' 32 | 33 | require 'functor/module/method_space' 34 | 35 | require 'functor/object/against' 36 | 37 | require 'functor/object_space/reflect' 38 | 39 | require 'functor/string/file' 40 | require 'functor/string/linear' 41 | 42 | require 'functor/symbol/as_s' 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/functor/hash/data.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class Hash 4 | 5 | # Access to a hash as if it were an OpenStruct. 6 | # 7 | # h = {:a=>1, :b=>2} 8 | # 9 | # h.data.a #=> 1 10 | # h.data.b #=> 2 11 | # h.data.c #=> nil 12 | # 13 | # h.data.c = 3 14 | # h.data.c #=> 3 15 | # 16 | # h.data.a? #=> true 17 | # h.data.d? #=> false 18 | # 19 | # TODO: Change name of Hash#data to something better? 20 | # 21 | # TODO: Is this really a method worth having? 22 | # 23 | def data 24 | this = self 25 | Functor.new do |op, *a| 26 | case op.to_s 27 | when /\=$/ 28 | op = op.to_s.chomp('=') 29 | this[op] = a.first 30 | when /\?$/ 31 | op = op.to_s.chomp('?') 32 | this.key?(op.to_s) || this.key?(op.to_sym) 33 | when /\!$/ 34 | op = op.to_s.chomp('!') 35 | this[op] # ??? 36 | else 37 | this[op.to_s] || this[op.to_sym] 38 | end 39 | end 40 | end 41 | 42 | end 43 | 44 | -------------------------------------------------------------------------------- /lib/functor/hash/recursively.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | require 'functor/enumerable/recursively' 3 | 4 | class Hash 5 | 6 | # Apply a block to a hash, and recursively apply that block 7 | # to each sub-hash: 8 | # 9 | # h = {:a=>1, :b=>{:x=>1, :y=>2}} 10 | # h.recursively.map{ |k,v| [k.to_s, v] } 11 | # #=> [["a", 1], ["b", [["y", 2], ["x", 1]]]] 12 | # 13 | # The recursive iteration can be treated separately from the non-recursive 14 | # iteration by passing a block to the #recursive method: 15 | # 16 | # h = {:a=>1, :b=>{:x=>1, :y=>2}} 17 | # h.recursively{ |k,v| [k.to_s, v] }.map{ |k,v| [k.to_s, v.to_s] } 18 | # #=> [["a", "1"], ["b", [["y", "2"], ["x", "1"]]]] 19 | # 20 | def recursively(*types, &block) 21 | Recursor.new(self, *types, &block) 22 | end 23 | 24 | class Recursor < Enumerable::Recursor #:nodoc: 25 | def method_missing(op, &yld) 26 | yld = yld || lambda{ |k,v| [k,v] } # ? to_enum 27 | rec = @block || yld #lambda{ |k,v| [k,v] } 28 | @enum.__send__(op) do |k,v| 29 | case v 30 | when String # b/c of 1.8 31 | yld.call(k,v) 32 | when *@types 33 | res = v.recursively(*@types, &@block).__send__(op,&yld) 34 | rec.call(k, res) 35 | else 36 | yld.call(k,v) 37 | end 38 | end 39 | end 40 | end 41 | 42 | ## TODO: When no longer need 1.8.6 support. 43 | =begin 44 | def recursively(*types, &block) 45 | types = types.empty? ? [self.class] : types 46 | Functor.new do |op, &yld| 47 | rec = block || yld 48 | __send__(op) do |k,v| 49 | case v 50 | when *types 51 | rec.call(k, v.recursively(*types, &block).__send__(op,&yld)) 52 | else 53 | yld.call(k,v) 54 | end 55 | end 56 | end 57 | end 58 | =end 59 | 60 | end 61 | 62 | -------------------------------------------------------------------------------- /lib/functor/kernel/as.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Returns an As-functor that allows one to call any 6 | # ancestor method directly on the given object. 7 | # 8 | # class AsExample1 9 | # def x ; 1 ; end 10 | # end 11 | # 12 | # class AsExample2 < AsExample1 13 | # def x ; 2 ; end 14 | # end 15 | # 16 | # class AsExample3 < AsExample2 17 | # def x ; as(AsExample1).x ; end 18 | # end 19 | # 20 | # AsExample1.new.x #=> 1 21 | # 22 | def as(ancestor, &blk) 23 | s = self 24 | r = Functor.new do |op, *a, &b| 25 | ancestor.instance_method(op).bind(self).call(*a, &b) 26 | end 27 | # TODO: Not 100% certain about how best to use #as block, if at all. 28 | r.instance_eval(&blk) if block_given? #yield(r) if block_given? 29 | return r 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /lib/functor/kernel/as_new.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Take an object as the initializing argument of a given class 6 | # and return a Functor instance ready to act on this new object. 7 | # 8 | # [1,2,3].every.as_new(Array).to_a 9 | # #=> [ [nil], [nil, nil], [nil, nil, nil] ] 10 | # 11 | def as_new(klass) 12 | s = self 13 | Functor.new do |op, *a, &b| 14 | klass.new(s).public_send(op, *a, &b) 15 | end 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /lib/functor/kernel/eigen.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Call methods on the eigenclass (i.e. the singleton_class). 6 | # 7 | # name = "Tom" 8 | # name.eigen.define_method(:turkey){ self + " Turkey" } 9 | # name.turkey #=> "Tom Turkey" 10 | # 11 | # One the nice things you can do with #eigen, is define class attributes 12 | # without having to open a `class << self` block. 13 | # 14 | # c = Class.new do 15 | # eigen.attr_accessor :a 16 | # end 17 | # c.a = 1 18 | # c.a #=> 1 19 | # 20 | # NOTE: This was once called `#meta`, but meta is such a generic 21 | # and overly used term that 'eigen' was decided upon as a better 22 | # choice. You can thank or blame _why for the term, if you like. 23 | # 24 | def eigen 25 | Functor.new do |op, *a, &b| 26 | (class << self; self; end).class_eval do 27 | __send__(op, *a, &b) 28 | end 29 | end 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /lib/functor/kernel/ergo.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Yield self -or- return self. 6 | # 7 | # "a".ergo.upcase #=> "A" 8 | # nil.ergo.foobar #=> nil 9 | # 10 | # "a".ergo{ |o| o.upcase } #=> "A" 11 | # nil.ergo{ |o| o.foobar } #=> nil 12 | # 13 | # This is like #tap, but #tap yields self and returns self, 14 | # where as #ergo yields self but returns the result. 15 | # 16 | # CREDIT: Daniel DeLorme 17 | 18 | def ergo(&b) 19 | if block_given? 20 | b.arity == 1 ? yield(self) : instance_eval(&b) 21 | else 22 | self 23 | end 24 | end 25 | 26 | end 27 | 28 | class NilClass 29 | 30 | FUNCTOR = Functor.new{ nil } 31 | 32 | # Compliments Kernel#ergo. 33 | # 34 | # "a".ergo{ |o| o.upcase } #=> "A" 35 | # nil.ergo{ |o| o.bar } #=> nil 36 | # 37 | # CREDIT: Daniel DeLorme 38 | 39 | def ergo 40 | FUNCTOR unless block_given? 41 | end 42 | 43 | end 44 | 45 | -------------------------------------------------------------------------------- /lib/functor/kernel/meta.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Call methods on the eigenclass (i.e. the singleton_class). 6 | # 7 | # name = "Tom" 8 | # name.eigen.define_method(:turkey){ self + " Turkey" } 9 | # name.turkey #=> "Tom Turkey" 10 | # 11 | # One of the nice things you can do with #eigen is define class attributes 12 | # without having to open a `class << self` block. 13 | # 14 | # c = Class.new do 15 | # meta.attr_accessor :a 16 | # end 17 | # c.a = 1 18 | # c.a #=> 1 19 | # 20 | def meta 21 | Functor.new do |op,*a,&b| 22 | (class << self; self; end).class_eval do 23 | __send__(op,*a,&b) 24 | end 25 | end 26 | end 27 | 28 | def eigen 29 | warn "The `eigen' method is deprecated. Please use `meta' instead." 30 | meta 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/functor/kernel/not.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Inversion functor. 6 | # 7 | # true.not.nil? #=> true 8 | # 9 | def not 10 | Functor.new do |op, *a, &b| 11 | !__send__(op, *a, &b) 12 | end 13 | end 14 | 15 | # Same as using NOT operator '!'. 16 | # 17 | # true.nil?.not? == !true.nil? 18 | # 19 | def not? 20 | !self 21 | end 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/functor/kernel/respond.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Like #respond_to? but returns the result of the call 6 | # if it does indeed respond. 7 | # 8 | # class RespondExample 9 | # def f; "f"; end 10 | # end 11 | # 12 | # x = RespondExample.new 13 | # x.respond(:f) #=> "f" 14 | # x.respond(:g) #=> nil 15 | # 16 | # or 17 | # 18 | # x.respond.f #=> "f" 19 | # x.respond.g #=> nil 20 | # 21 | # This method was known as #try until Rails defined #try 22 | # to be something more akin to #ergo. 23 | # 24 | # TODO: Not 100% sure `respond` is best name for HOM form. 25 | # 26 | # CREDIT: Trans, Chris Wanstrath 27 | # 28 | def respond(sym=nil, *args, &blk) 29 | if sym 30 | return nil unless respond_to?(sym) 31 | __send__(sym, *args, &blk) 32 | else 33 | ## Better definition? 34 | ##Functor.new do |op, *a, &b| 35 | ## respond(op, *a, &b) 36 | ##end 37 | Functor.new(&method(:respond).to_proc) 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /lib/functor/kernel/tap.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # The tap K-Combinator. This yields self -and- returns self. 6 | # 7 | # 'foo.yml'.tap{ |f| YAML.load(f) } #=> 'foo.yml' 8 | # 9 | # Unlike Ruby's definition, this rendition can be used as a higher 10 | # order message. This form allows a single call before returning 11 | # the receiver. 12 | # 13 | # YAML.tap.load_file('foo.yml').load_file('bar.yml') 14 | # 15 | # IMPORTANT: This is a core override! 16 | 17 | def tap #:yield: 18 | if block_given? 19 | yield(self) 20 | self 21 | else 22 | Functor.new{ |op,*a,&b| self.send(op, *a, &b); self } 23 | end 24 | end 25 | 26 | # Old Definition: 27 | # 28 | # def tap #:yield: 29 | # if block_given? 30 | # b.arity == 1 ? yield(self) : instance_eval(&b) 31 | # end 32 | # return self 33 | # end 34 | # 35 | 36 | end 37 | -------------------------------------------------------------------------------- /lib/functor/kernel/try.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module Kernel 4 | 5 | # Invokes the method identified by the symbol `method`, passing it any 6 | # arguments and/or the block specified, just like the regular Ruby 7 | # `Object#send` does. 8 | # 9 | # *Unlike* that method however, a `NoMethodError` exception will *not* 10 | # be raised and `nil` will be returned instead, if the receiving object 11 | # is a `nil` object or NilClass. 12 | # 13 | # For example, without try 14 | # 15 | # @example = Struct.new(:name).new("bob") 16 | # 17 | # @example && @example.name 18 | # 19 | # or: 20 | # 21 | # @example ? @example.name : nil 22 | # 23 | # But with try 24 | # 25 | # @example.try(:name) #=> "bob" 26 | # 27 | # or 28 | # 29 | # @example.try.name #=> "bob" 30 | # 31 | # It also accepts arguments and a block, for the method it is trying: 32 | # 33 | # @people.try(:collect){ |p| p.name } 34 | # 35 | def try(method=nil, *args, &block) 36 | if method 37 | __send__(method, *args, &block) 38 | else 39 | self 40 | end 41 | end 42 | 43 | end 44 | 45 | 46 | class NilClass 47 | 48 | # See Kernel#try. 49 | def try(method=nil, *args) 50 | if method 51 | nil 52 | else 53 | Functor.new{ nil } 54 | end 55 | end 56 | 57 | end 58 | 59 | -------------------------------------------------------------------------------- /lib/functor/module/method_space.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | require 'facets/module/basename' # FIXME: remove dependency 3 | 4 | class Module 5 | 6 | # Create method namespaces, allowing for method 7 | # chains but still accessing the object's instance. 8 | # 9 | # class A 10 | # attr_writer :x 11 | # method_space :inside do 12 | # def x; @x; end 13 | # end 14 | # end 15 | # 16 | # a = A.new 17 | # a.x = 10 18 | # a.inside.x #=> 10 19 | # 20 | # expect NoMethodError do 21 | # a.x 22 | # end 23 | # 24 | # NOTE: This method is not a common core extension and is not 25 | # loaded automatically when using require 'facets'. 26 | # 27 | # CREDIT: Pit Captain 28 | # 29 | # @uncommon 30 | # require 'facets/module/method_space' 31 | # 32 | def method_space(name, mod=nil, &blk) 33 | 34 | ## If block is given then create a module, otherwise 35 | ## get the name of the module. 36 | if block_given? 37 | name = name.to_s 38 | raise ArgumentError if mod 39 | mod = Module.new(&blk) 40 | else 41 | if Module === name 42 | mod = name 43 | name = mod.basename.downcase 44 | end 45 | mod = mod.dup 46 | end 47 | 48 | ## Include the module. This is neccessary, otherwise 49 | ## Ruby won't let us bind the instance methods. 50 | include mod 51 | 52 | ## Save the instance methods of the module and 53 | ## replace them with a "transparent" version. 54 | methods = {} 55 | mod.instance_methods(false).each do |m| 56 | methods[m.to_sym] = mod.instance_method(m) 57 | mod.module_eval %{ 58 | def #{m}(*a,&b) 59 | super(*a,&b) 60 | end 61 | } 62 | ##mod.instance_eval do 63 | ## define_method(m) 64 | ## super 65 | ## end 66 | ##end 67 | end 68 | 69 | ## Add a method for the namespace that delegates 70 | ## via the Functor to the saved instance methods. 71 | define_method(name) do 72 | mtab = methods 73 | Functor.new do |op, *args| 74 | if meth = mtab[op.to_sym] 75 | meth.bind(self).call(*args) 76 | else 77 | #self.__send__(op, *args) 78 | raise NoMethodError, "undefined method `#{m}'" 79 | end 80 | end 81 | end 82 | end 83 | 84 | # Include a module via a specified space. 85 | # 86 | # module T 87 | # def t ; "HERE" ; end 88 | # end 89 | # 90 | # class X 91 | # include_as :test => T 92 | # def t ; test.t ; end 93 | # end 94 | # 95 | # X.new.t #=> "HERE" 96 | # 97 | # NOTE: This method is not a common core extension and is not 98 | # loaded automatically when using require 'facets'. 99 | # 100 | # @uncommon 101 | # require 'facets/module/method_space' 102 | # 103 | def include_as(h) 104 | h.each{ |name, mod| method_space(name, mod) } 105 | end 106 | 107 | end 108 | -------------------------------------------------------------------------------- /lib/functor/object/against.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class Object 4 | 5 | # Objectified message or block application. Only a message 6 | # or a block can be given, not both. 7 | # 8 | # msg - method and arguments [Array] 9 | # blk - procedure block [Proc] 10 | # 11 | # Examples 12 | # 13 | # a = [1,2,3,4,5] 14 | # c = a.against(:>, 2) 15 | # c.select #=> [3,4,5] 16 | # 17 | # a = [1,2,3,4,5] 18 | # c = a.against(:>) 19 | # c.select(2) #=> [3,4,5] 20 | # 21 | # Returns [Functor] 22 | # 23 | # TODO: Better name for this method? 24 | 25 | def against(*msg, &blk) 26 | raise ArgumentError, "too many arguments" if blk && !msg.empty? 27 | 28 | this = self 29 | 30 | blk = ::Proc.new{ |x,*a| x.__send__(*msg, *a) } unless blk 31 | 32 | #if blk 33 | Functor.new do |m, *a, &b| 34 | if b 35 | b2 = ::Proc.new{ |*x| blk.call(*b.call(*x), *a) } 36 | else 37 | b2 = blk 38 | end 39 | this.__send__(m, &b2) 40 | end 41 | #else 42 | # Functor.new do |m, *a, &b| 43 | # if b 44 | # b2 = ::Proc.new{ |*x| b.call(*x).__send__(*msg, *a) } 45 | # else 46 | # b2 = ::Proc.new{ |x| x.__send__(*msg, *a) } 47 | # end 48 | # this.__send__(m, &b2) 49 | # end 50 | #end 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /lib/functor/object_space/reflect.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | module ObjectSpace 4 | 5 | # Reflection ensures that information about an object 6 | # is actual according to Ruby's Kernel definitions, just 7 | # in case such methods have been overridden. 8 | # 9 | # ObjectSpace.reflect("object").object_id 10 | # 11 | # There is also a global short-cut for this method to ease 12 | # meta-programming with it. 13 | # 14 | # $ref["object"].class 15 | # 16 | # Typically theis method will be used to gather the object's 17 | # id, as in the example given, or it's class, but any Kernel 18 | # method can be used. 19 | # 20 | # Care should be taken in utilizing this technique. In most 21 | # cases it is not needed, but in certain cases is useful 22 | # for improving the robustness of meta-programming solutions. 23 | # 24 | # Note that this is also equivalent to using +as(Kernel)+ ... 25 | # 26 | # "object".as(Kernel).object_id 27 | # 28 | # But obviously, in this case there is the risk that #as has 29 | # be overridden too. 30 | # 31 | def self.reflect(obj) 32 | Functor.new do |op, *a, &b| 33 | Kernel.instance_method(op).bind(obj).call(*a, &b) 34 | end 35 | end 36 | 37 | end 38 | 39 | # Shorcut for +ObjectSpace.reflect+. 40 | # 41 | # $ref["object"].class 42 | # 43 | $ref = lambda do |obj| 44 | ObjectSpace.reflect(obj) 45 | end 46 | 47 | -------------------------------------------------------------------------------- /lib/functor/string/file.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class String 4 | 5 | # Use fluent notation for calling File functions. 6 | # 7 | # For instance, if we had a file 'foo.txt', 8 | # 9 | # 'foo.txt'.file.mtime 10 | # 11 | def file 12 | fn = self 13 | Functor.new do |op, *a, &b| 14 | File.send(op, fn, *a, &b) 15 | end 16 | end 17 | 18 | end 19 | 20 | -------------------------------------------------------------------------------- /lib/functor/string/linear.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class String 4 | 5 | # Like `#newlines` but returns a Functor instead. 6 | # 7 | # "a \n b \n c".linear.strip #=> "a\nb\nc" 8 | # 9 | def linear 10 | Functor.new do |op, *a, &b| 11 | lines.map { |line| 12 | line.chomp.public_send(op, *a, &b) 13 | }.join("\n") 14 | end 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/functor/symbol/as_s.rb: -------------------------------------------------------------------------------- 1 | require 'functor' 2 | 3 | class Symbol 4 | 5 | # Convert symbol to string, apply string method and convert 6 | # back to symbol via a fluent interface. 7 | # 8 | # :HELLO.as_s.downcase #=> :hello 9 | # 10 | def as_s 11 | Functor.new do |op, *a| 12 | to_s.send(op, *a).to_sym 13 | end 14 | end 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /lib/functor/version.rb: -------------------------------------------------------------------------------- 1 | class Functor 2 | 3 | # 4 | def self.index 5 | @index ||= ( 6 | require 'yaml' 7 | YAML.load_file(File.dirname(__FILE__) + '/functor.yml') 8 | ) 9 | end 10 | 11 | # 12 | def self.const_missing(const_name) 13 | name = const_name.to_s.downcase 14 | index.key?(name) ? index[name] : super(const_name) 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /test/case_functor.rb: -------------------------------------------------------------------------------- 1 | covers 'facets/functor' 2 | 3 | test_case Functor do 4 | 5 | class_method :new do 6 | 7 | test do 8 | fc = Functor.new do |op, *a, &b| 9 | [op, a, b] 10 | end 11 | (fc + 1).assert == [:+, [1], nil] 12 | end 13 | 14 | end 15 | 16 | method :__class__ do 17 | 18 | test do 19 | fc = Functor.new do |op, *a, &b| 20 | [op, a, b] 21 | end 22 | fc.__class__.assert == Functor 23 | end 24 | 25 | end 26 | 27 | method :to_proc do 28 | 29 | test do 30 | f = Functor.new do |op, *a| 31 | [op, *a] 32 | end 33 | p = f.to_proc 34 | p.assert.is_a? Proc 35 | p.call(:+,1,2,3).assert == [:+,1,2,3] 36 | end 37 | 38 | end 39 | 40 | end 41 | 42 | --------------------------------------------------------------------------------