call_by_need 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +3 -0
- data/Gemfile.lock +26 -0
- data/Rakefile +9 -0
- data/call_by_need.gemspec +19 -0
- data/lib/call_by_need/call_by_need.rb +63 -0
- data/lib/call_by_need/context.rb +22 -0
- data/lib/call_by_need/version.rb +3 -0
- data/lib/call_by_need.rb +3 -0
- data/readme.md +3 -0
- data/spec/call_by_need_spec.rb +101 -0
- metadata +91 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
call_by_need (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
rake (10.0.4)
|
11
|
+
rspec (2.5.0)
|
12
|
+
rspec-core (~> 2.5.0)
|
13
|
+
rspec-expectations (~> 2.5.0)
|
14
|
+
rspec-mocks (~> 2.5.0)
|
15
|
+
rspec-core (2.5.2)
|
16
|
+
rspec-expectations (2.5.0)
|
17
|
+
diff-lcs (~> 1.1.2)
|
18
|
+
rspec-mocks (2.5.0)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
call_by_need!
|
25
|
+
rake
|
26
|
+
rspec
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/call_by_need/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["fronx"]
|
6
|
+
gem.email = ["fronx@wurmus.de"]
|
7
|
+
gem.description = %q{A little call-by-need implementation for recreational use.}
|
8
|
+
gem.summary = %q{A scoped call-by-need module/class.}
|
9
|
+
gem.homepage = "https://github.com/fronx/call_by_need.rb"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.name = "call_by_need"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = CallByNeed::VERSION
|
16
|
+
|
17
|
+
gem.add_development_dependency 'rake'
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CallByNeed
|
2
|
+
class VarMissingError < StandardError
|
3
|
+
def initialize(name, context)
|
4
|
+
super("`#{name}` was expected, but doesn't exist in context `#{context.inspect}`.")
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class DuplicateVarError < StandardError
|
9
|
+
def initialize(name, context)
|
10
|
+
super("`#{name}` can't be initialized twice in context `#{context.inspect}`.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def declare(name, &block)
|
15
|
+
var_set(name, block)
|
16
|
+
class << self; self; end.class_eval do
|
17
|
+
define_method(name) do
|
18
|
+
get(name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def declared?(name)
|
24
|
+
store.has_key?(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(name)
|
28
|
+
if var_get(name).respond_to?(:call)
|
29
|
+
store[name] = var_get(name).call(self)
|
30
|
+
else
|
31
|
+
var_get(name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def keys
|
36
|
+
store.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
var_get(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def var_get(name)
|
45
|
+
if store.has_key?(name)
|
46
|
+
store[name]
|
47
|
+
else
|
48
|
+
raise VarMissingError.new(name, self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def var_set(name, value)
|
53
|
+
if store.has_key?(name) && (store[name] != value)
|
54
|
+
raise DuplicateVarError.new(name, self)
|
55
|
+
else
|
56
|
+
store[name] = value # returns value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def store
|
61
|
+
@__store ||= {}
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CallByNeed
|
2
|
+
class Context
|
3
|
+
include CallByNeed
|
4
|
+
|
5
|
+
def initialize(*others, &block)
|
6
|
+
others.each do |other|
|
7
|
+
other.keys.each do |k|
|
8
|
+
if other[k].respond_to?(:call)
|
9
|
+
declare(k, &other[k])
|
10
|
+
else # item has already been evaluated
|
11
|
+
declare(k) { other[k] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
block.call(self) if block
|
16
|
+
end
|
17
|
+
|
18
|
+
def merge(*others)
|
19
|
+
self.class.new(self, *others)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/call_by_need.rb
ADDED
data/readme.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'call_by_need'
|
3
|
+
|
4
|
+
class CallByNeedTest
|
5
|
+
include CallByNeed
|
6
|
+
attr_reader :a
|
7
|
+
def initialize(a)
|
8
|
+
@a = a
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe CallByNeed do
|
13
|
+
let(:object) do
|
14
|
+
CallByNeedTest.new(123)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "executes in the calling scope" do
|
18
|
+
@a = nil
|
19
|
+
object.a.should == 123
|
20
|
+
object.declare(:foo) { @a = 3 * 9 }
|
21
|
+
object.foo.should == 27
|
22
|
+
@a.should == 27
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe CallByNeed::Context do
|
27
|
+
def something
|
28
|
+
@x ||= "something"
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:context) do
|
32
|
+
CallByNeed::Context.new do |c|
|
33
|
+
c.declare(:a) { something }
|
34
|
+
c.declare(:b) { something + something }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "disallows overwriting values" do
|
39
|
+
lambda do
|
40
|
+
context.declare(:a) { "boo" }
|
41
|
+
end.should raise_error(CallByNeed::DuplicateVarError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "throws an error when reading nonexisting values" do
|
45
|
+
lambda do
|
46
|
+
context.get(:x)
|
47
|
+
end.should raise_error(CallByNeed::VarMissingError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can read values repeatedly" do
|
51
|
+
context.a.should == 'something'
|
52
|
+
context.a.should == 'something'
|
53
|
+
end
|
54
|
+
|
55
|
+
it "executes the block for a name once" do
|
56
|
+
context.a.should == 'something' # cached hereafter
|
57
|
+
@x = 'else'
|
58
|
+
context.a.should == 'something' # memoized
|
59
|
+
context.b.should == 'elseelse' # evaluated later --> different value
|
60
|
+
end
|
61
|
+
|
62
|
+
it "allows self-references" do
|
63
|
+
context = CallByNeed::Context.new do |c|
|
64
|
+
c.declare(:x) { 2 * c.y }
|
65
|
+
c.declare(:z) { 3 * c.x }
|
66
|
+
c.declare(:y) { 4 }
|
67
|
+
end
|
68
|
+
context.z.should == 24
|
69
|
+
end
|
70
|
+
|
71
|
+
it "can be merged" do
|
72
|
+
c1 = CallByNeed::Context.new { |c| c.declare(:a) { 1 } }
|
73
|
+
c2 = CallByNeed::Context.new { |c| c.declare(:b) { 2 } }
|
74
|
+
c3 = c1.merge(c2)
|
75
|
+
c3.a.should == 1
|
76
|
+
c3.b.should == 2
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can inherit" do
|
80
|
+
c1 = CallByNeed::Context.new { |c| c.declare(:a) { 2 } }
|
81
|
+
c2 = CallByNeed::Context.new(c1) { |c| c.declare(:b) { c.a * 4 } }
|
82
|
+
c2.a.should == 2
|
83
|
+
c2.b.should == 8
|
84
|
+
end
|
85
|
+
|
86
|
+
it "doesn't care what the order of dependent declarations is" do
|
87
|
+
CallByNeed::Context.new do |c|
|
88
|
+
c.declare(:a, &:b)
|
89
|
+
c.declare(:b) { 'b' }
|
90
|
+
end.a.should == 'b'
|
91
|
+
c1 = CallByNeed::Context.new do |c|
|
92
|
+
c.declare(:a, &:b)
|
93
|
+
c.declare(:x) { |x| x.b + 'x' }
|
94
|
+
end
|
95
|
+
c2 = CallByNeed::Context.new do |c|
|
96
|
+
c.declare(:b) { 'b' }
|
97
|
+
end
|
98
|
+
c2.merge(c1).a.should == 'b'
|
99
|
+
c2.merge(c1).x.should == 'bx'
|
100
|
+
end
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: call_by_need
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- fronx
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2013-09-07 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
description: A little call-by-need implementation for recreational use.
|
38
|
+
email:
|
39
|
+
- fronx@wurmus.de
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- Gemfile
|
48
|
+
- Gemfile.lock
|
49
|
+
- Rakefile
|
50
|
+
- call_by_need.gemspec
|
51
|
+
- lib/call_by_need.rb
|
52
|
+
- lib/call_by_need/call_by_need.rb
|
53
|
+
- lib/call_by_need/context.rb
|
54
|
+
- lib/call_by_need/version.rb
|
55
|
+
- readme.md
|
56
|
+
- spec/call_by_need_spec.rb
|
57
|
+
homepage: https://github.com/fronx/call_by_need.rb
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 1747255289361994175
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 1747255289361994175
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: A scoped call-by-need module/class.
|
90
|
+
test_files:
|
91
|
+
- spec/call_by_need_spec.rb
|