fmap 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -0
- data/README.md +75 -0
- data/lib/fmap.rb +72 -0
- 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.
|
data/README.md
ADDED
@@ -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.
|
data/lib/fmap.rb
ADDED
@@ -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: []
|