Skip to content

Commit e01857f

Browse files
committed
Port remaining facets methods that use functor.
1 parent 67973f7 commit e01857f

11 files changed

Lines changed: 545 additions & 0 deletions

File tree

lib/functor/array/recursively.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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<Class>]
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

lib/functor/enumerable/apply.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+

lib/functor/extensions.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
require 'functor'
22

3+
require 'functor/array/recursively'
4+
35
require 'functor/enumerable/accrue'
46
require 'functor/enumerable/accumulate'
57
require 'functor/enumerable/all'
68
require 'functor/enumerable/any'
9+
require 'functor/enumerable/apply'
710
require 'functor/enumerable/every'
811
require 'functor/enumerable/ewise'
912
require 'functor/enumerable/having'
1013
require 'functor/enumerable/in_order_of'
1114
require 'functor/enumerable/per'
15+
require 'functor/enumerable/recursively'
1216
require 'functor/enumerable/where'
1317

1418
require 'functor/enumerator/fx'
1519

1620
require 'functor/hash/data'
21+
require 'functor/hash/recursively'
1722

1823
require 'functor/kernel/as'
1924
require 'functor/kernel/as_new'
2025
require 'functor/kernel/eigen'
26+
require 'functor/kernel/ergo'
27+
require 'functor/kernel/meta'
2128
require 'functor/kernel/not'
2229
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'
2336

2437
require 'functor/object_space/reflect'
2538

@@ -28,3 +41,5 @@
2841

2942
require 'functor/symbol/as_s'
3043

44+
45+

lib/functor/hash/recursively.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+

lib/functor/kernel/ergo.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+

lib/functor/kernel/meta.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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

lib/functor/kernel/tap.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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

0 commit comments

Comments
 (0)