fmap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +9 -0
  2. data/README.md +75 -0
  3. data/lib/fmap.rb +72 -0
  4. metadata +49 -0
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2011 Sean Keith McAuley
2
+
3
+ This software is MIT-licensed.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ # About
2
+
3
+ The `fmap` library defines three methods on all object instances, `Object#fmap`, `Object#afmap`, and `Object#eqfmap`. All three methods are ways to "descend into" arbitrarily-nested data structures to an arbitrary depth, replacing each object in the structure with the return value of the supplied block. This makes these methods a generalization of `Enumerable#map`, that, instead of applying to all Enumerable objects, apply to all *functors*.
4
+
5
+ From the perspective of the `fmap` library, a *functor* is anything that has a `#functor?` method which returns true. This, by default, includes all built-in container objects, such as Array, Hash, Range, and Set, and also anything which imports the `Enumerable` module (where `Enumerable#fmap` is defined in terms of `Enumerable#map`.) It also includes `Object::Composite`, a module that is brought in by the `fmap` library which you can import to make any simple data structure class (where "simple" means that every instance variable is an exposed value of the container) `fmap`pable.
6
+
7
+ # Usage
8
+
9
+ `Object#fmap` is the most general method; it will simply yield every object in the data structure. However, this means that `Object#fmap` also yield the data structure itself. If you don't want this behavior, use `Object#afmap`, which will only yield "atomic" (non-*functor*) values. A comparison:
10
+
11
+ require 'fmap'
12
+ require 'set'
13
+
14
+ stuff = Set[{[1, 2, 3] => [4, 5, 6, true]}, -3.5, :hmm]
15
+
16
+ stuff.afmap{ |v| v.inspect } # => #<Set: {{["1", "2", "3"]=>["4", "5", "6", "true"]}, "-3.5", ":hmm"}>
17
+
18
+ stuff.fmap{ |v| v.inspect } # => "#<Set: {\"{\\\"[\\\\\\\"1\\\\\\\", \\\\\\\"2\\\\\\\", \\\\\\\"3\\\\\\\"]\\\"=>\\\"[\\\\\\\"4\\\\\\\", \\\\\\\"5\\\\\\\", \\\\\\\"6\\\\\\\", \\\\\\\"true\\\\\\\"]\\\"}\", \"-3.5\", \":hmm\"}>"
19
+
20
+ The trivial use of `Object#fmap`, using the identity combinator (`id = lambda{ |v| v }`), produces a deep copy without using serialization:
21
+
22
+ require 'fmap'
23
+
24
+ stuff = Set[{[1, 2, 3] => [4, 5, 6, true]}, -3.5, :hmm]
25
+ new_stuff = stuff.fmap{ |v| v }
26
+
27
+ `Object#eqfmap` takes a type object as an argument. The type object is used in an equivalence-class comparison (`Object#===`) to determine whether to yield each value.
28
+
29
+ require 'fmap'
30
+
31
+ stuff = Set[{[-1, 2, 3] => [4, :x, 82, 6, true]}, -3.5, :hmm]
32
+ stuff_without_symbols = stuff.eqfmap(Symbol){ |v| v.to_s }
33
+ stuff_without_symbols # => #<Set: {{[-1, 2, 3]=>[4, "x", 82, 6, true]}, -3.5, "hmm"}>
34
+
35
+ ranged_stuff = stuff.eqfmap(Numeric){ |v| [[v, 0].max, 50].min }
36
+ ranged_stuff # => #<Set: {{[0, 2, 3]=>[4, "x", 50, 6, true]}, 0, "hmm"}>
37
+
38
+
39
+ # Using `fmap` with your own data structures
40
+
41
+ You can add `fmap` support to your own data structures in three ways:
42
+
43
+ 1. You can simply make your class `Enumerable`. When `Object#fmap` is called on your class's instances, they will be iterated through using `Enumerable#map`, and the result will be an Array.
44
+
45
+ 2. You can include the module `Object::Composite` in your class. For each object of this type, a shallow clone will be made of it; then each instance variable set on the instance will be read, `fmap`ped itself, yielded, and then overwritten with the yielded value. This is very likely inefficient in most implementations.
46
+
47
+ 3. You can define your own `#fmap` and `#functor?` methods. (`Object#afmap` and `Object#eqfmap` are both defined in terms of `Object#fmap`.)
48
+
49
+ An `#fmap`-supporting class looks like this:
50
+
51
+ class Foo
52
+ def initialize(a, b)
53
+ @attr_a = a
54
+ @attr_b = b
55
+ end
56
+
57
+ ...
58
+
59
+ def functor?
60
+ true
61
+ end
62
+
63
+ def fmap(&bl)
64
+ new_attr_a = @attr_a.fmap(&bl)
65
+ new_attr_b = @attr_b.fmap(&bl)
66
+ new_inst = self.class.new(new_attr_a, new_attr_b)
67
+ bl.call( new_inst )
68
+ end
69
+ end
70
+
71
+ There are three parts to the `#fmap` definition:
72
+
73
+ 1. Call `#fmap` on your own values, passing on the block passed to you.
74
+ 2. Create a new instance of yourself with the results of those `#fmap` calls as the new data.
75
+ 3. Finally, yield that new instance to the block, and return the value returned from the block.
@@ -0,0 +1,72 @@
1
+ class Object
2
+ module Composite
3
+ def fmap(&bl)
4
+ begin
5
+ new_inst = self.clone
6
+ rescue TypeError
7
+ return self
8
+ end
9
+
10
+ new_inst.instance_variables.each do |iv_name|
11
+ iv_val = new_inst.instance_variable_get(iv_name)
12
+ fmapped_val = bl.call( iv_val.fmap(&bl) )
13
+ new_inst.instance_variable_set(iv_name, fmapped_val)
14
+ end
15
+ end
16
+
17
+ def functor?
18
+ true
19
+ end
20
+ end
21
+
22
+ def functor?
23
+ false
24
+ end
25
+
26
+ def fmap
27
+ yield( self )
28
+ end
29
+
30
+ def afmap
31
+ self.fmap{ |v| v.functor? ? v : yield(v) }
32
+ end
33
+
34
+ def eqfmap(type)
35
+ self.fmap{ |v| (type === v) ? yield(v) : v }
36
+ end
37
+ end
38
+
39
+ module Enumerable
40
+ def functor?
41
+ true
42
+ end
43
+
44
+ def fmap(&bl)
45
+ bl.call( self.map{ |v| v.fmap(&bl) } )
46
+ end
47
+ end
48
+
49
+ class Range
50
+ def fmap(&bl)
51
+ bl.call( self.class.new(
52
+ self.first.fmap(&bl),
53
+ self.last.fmap(&bl),
54
+ self.exclude_end?) )
55
+ end
56
+ end
57
+
58
+ class Hash
59
+ def fmap(&bl)
60
+ new_h = self.class.new
61
+ self.each do |k, v|
62
+ new_h[k.fmap(&bl)] = v.fmap(&bl)
63
+ end
64
+ bl.call( new_h )
65
+ end
66
+ end
67
+
68
+ class Set
69
+ def fmap(&bl)
70
+ bl.call( self.map{ |v| v.fmap(&bl) }.to_set )
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fmap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sean Keith McAuley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-22 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: fmap provides a "deep map" method that maps over any mix of arbitrarily-nested
15
+ data-structures
16
+ email: sean@zarokeanpie.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - LICENSE
23
+ - lib/fmap.rb
24
+ homepage: http://github.com/derefr/fmap
25
+ licenses: []
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 1.8.8
45
+ signing_key:
46
+ specification_version: 3
47
+ summary: fmap provides a "deep map" method that maps over any mix of arbitrarily-nested
48
+ data-structures
49
+ test_files: []