lambda-calculus 0.1.0 → 0.1.4
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.
- checksums.yaml +8 -8
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.md +68 -0
- data/Rakefile +11 -0
- data/lambda-calculus.gemspec +15 -0
- data/lib/lambda-calculus.rb +118 -71
- data/spec/lambda_calculus_spec.rb +143 -0
- data/spec/spec_helper.rb +9 -0
- metadata +34 -8
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Y2FlZmU1ZmMwMDMwOWUyYmYyOWYyN2M5YzQ4NzM3MTliMDA4NjExZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YjQwZTU5MWFhZWU4NzM1ZmZjODI2YmJiYWY5Nzg0MmMyZGRmNjY2Zg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OWRhYTFiYzBmODg5YWFlYTYyYTM1MTM3ZTViYjAwNjBmNDUwNGYwYTM4NmJl
|
10
|
+
NmE4YTE0NTUzNzc2MTRhMzdmOTk0YjY2ZDhlNDZhYmJlOTk2OWEyZDI1MDYw
|
11
|
+
YWJkNGQzMTFhODU1NWVmYzU1OThmNDE5MDE5OWJlMGU4YjM1ODI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NDRlODAwM2NhYTA0ZjNlYTgyOTkwODg0NWRiODg2Nzk0ZTY2ODI3ZDlhNzk5
|
14
|
+
NzY3YjIxNmQyOGIxZTIxZWM1ZjdkMWZkZTY0MzAxYmY4NjJiYmYyNzNhNTkx
|
15
|
+
ZTIyNWIxODFhN2QwMzA5YWY4ZGU2NTQ3MDk3MGYwMjQ1Yjc1NGQ=
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Alex Altair
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
Lambda calculus engine [](http://badge.fury.io/rb/lambda-calculus)
|
2
|
+
======================
|
3
|
+
|
4
|
+
Ever wanted to evaluate lambda expressions from the command line? Well now you can!
|
5
|
+
1. Download this gem; `gem install lambda-calculus`.
|
6
|
+
2. Open up pry or irb and type `require 'lambda-calculus'`.
|
7
|
+
3. Declare a lambda expression; `my_exp = LambdaExpression.new('(\x.xy)(\a.a)')`
|
8
|
+
4. Evaluate or beta reduce!
|
9
|
+
```
|
10
|
+
> my_exp.beta_reduce
|
11
|
+
=> (\a.a)y
|
12
|
+
> my_exp.evaluate
|
13
|
+
=> y
|
14
|
+
```
|
15
|
+
Lambda expressions are a class. Their attributes depend on which kind they are, as defined by the following code snippet from lambda-calculus.rb;
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
case args.length
|
19
|
+
when 1
|
20
|
+
@kind = :variable
|
21
|
+
@value = node_value
|
22
|
+
when 2
|
23
|
+
@kind = :abstraction
|
24
|
+
@bound_var = node_value
|
25
|
+
@body = child1
|
26
|
+
when 3
|
27
|
+
@kind = :application
|
28
|
+
@function = child1
|
29
|
+
@argument = child2
|
30
|
+
```
|
31
|
+
Arguments
|
32
|
+
---------
|
33
|
+
`LambdaExpression.new()` has two modes for taking arguments; natively or naturally. I tried to make it as flexible as possible while matching expectations.
|
34
|
+
|
35
|
+
Natively it can take one, two or three arguments where one makes it a variable, two makes it an abstraction, and three makes it an application. In each case the first argument must be a symbol or string of the correct form; anything but * for variables and abstractions, and only * for applications. The other arguments can be LambdaExpressions, strings of the natural form, or symbols.
|
36
|
+
|
37
|
+
Naturally, one can just give the lambda expression written as a string, the way you might write an expression to someone over text.
|
38
|
+
|
39
|
+
Some valid examples are shown below;
|
40
|
+
|
41
|
+
This will become a variable;
|
42
|
+
```ruby
|
43
|
+
LambdaExpression.new(:x)
|
44
|
+
LambdaExpression.new('x')
|
45
|
+
```
|
46
|
+
These will become abstractions;
|
47
|
+
```ruby
|
48
|
+
LambdaExpression.new(:x, :x)
|
49
|
+
LambdaExpression.new('x', 'x')
|
50
|
+
LambdaExpression.new(:x, 'xy')
|
51
|
+
```
|
52
|
+
These will become applications;
|
53
|
+
```ruby
|
54
|
+
LambdaExpression.new(:*, :x, :y)
|
55
|
+
LambdaExpression.new('*', 'x', 'y')
|
56
|
+
LambdaExpression.new(:*, :x, '\a.bb')
|
57
|
+
```
|
58
|
+
These will be parsed as strings
|
59
|
+
```ruby
|
60
|
+
LambdaExpression.new('x')
|
61
|
+
LambdaExpression.new('xy')
|
62
|
+
LambdaExpression.new('(\x.xy)(\a.bb)')
|
63
|
+
LambdaExpression.new('\x.xy')
|
64
|
+
```
|
65
|
+
|
66
|
+
Being able to accept arbitrary strings means that a string parser is included.
|
67
|
+
The method `beta_reduce` will perform beta reduction, but only on the top-level application.
|
68
|
+
Methods for printing facts about lambda expressions are also included; `lambda_string_tester` for strings, and `lambda_tester` for native LambdaExpression objects.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :test => :spec
|
7
|
+
|
8
|
+
desc "Run specs as default activity."
|
9
|
+
task :default => :spec
|
10
|
+
# Check the RDoc for RakeTask for various options that you can optionally pass into the task definition.
|
11
|
+
# https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/rake_task.rb
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'lambda-calculus'
|
3
|
+
spec.version = '0.1.4'
|
4
|
+
spec.date = Time.now.strftime("%Y-%m-%d")
|
5
|
+
spec.summary = "A class with methods for creating and evaluating lambda expressions."
|
6
|
+
spec.description = "This gem allows users to create and evaluate lambda expression objects from natural string input like '(\\x.xy)(\\a.aba)'."
|
7
|
+
spec.authors = ["Alex Altair"]
|
8
|
+
spec.email = 'alexanderaltair@gmail.com'
|
9
|
+
spec.files = `git ls-files`.split("\n") - %w(.rvmrc .gitignore)
|
10
|
+
spec.homepage = 'https://github.com/alexaltair/lambda-calculus'
|
11
|
+
spec.license = 'MIT'
|
12
|
+
spec.test_files = `git ls-files -- spec/*`.split("\n")
|
13
|
+
spec.post_install_message = "* Check out the documentation at #{spec.homepage} *"
|
14
|
+
spec.add_development_dependency "rspec"
|
15
|
+
end
|
data/lib/lambda-calculus.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
# Takes a string and
|
1
|
+
# Takes a string and finds the index of the matching close paren to the open paren specified.
|
2
2
|
def close_paren_index(string, open_paren_index = 0)
|
3
3
|
if open_paren_index == 0
|
4
4
|
raise ArgumentError "First character not an open paren." unless string[open_paren_index] == '('
|
5
|
-
else
|
6
|
-
raise ArgumentError "Given index not an open paren." unless string[open_paren_index] == '('
|
5
|
+
else raise ArgumentError "Given index not an open paren." unless string[open_paren_index] == '('
|
7
6
|
end
|
8
7
|
array = string.split('')
|
9
8
|
depth = 1
|
@@ -22,16 +21,37 @@ end
|
|
22
21
|
|
23
22
|
class LambdaExpression
|
24
23
|
|
25
|
-
# Change the setters so that attributes cannot be changed to make a meaningless lambda expression.
|
24
|
+
# ----- Change the setters so that attributes cannot be changed to make a meaningless lambda expression.
|
26
25
|
attr_accessor :kind, :value, :bound_var, :body, :function, :argument
|
26
|
+
# ----- Make a class variable or something, which stores all the strategies; LambdaExpression.strategies = [:eager, :lazy, :depth_first ... ]
|
27
27
|
|
28
28
|
def initialize(*args)
|
29
|
+
# This deals with all possible types of arguments that could be given.
|
30
|
+
args = sanitize_args(*args)
|
31
|
+
node_value, child1, child2 = args
|
29
32
|
|
30
|
-
#
|
33
|
+
# At this point the first argument should be a symbol, and the rest LambdaExpressions.
|
34
|
+
case args.length
|
35
|
+
when 1
|
36
|
+
@kind = :variable
|
37
|
+
@value = node_value
|
38
|
+
when 2
|
39
|
+
@kind = :abstraction
|
40
|
+
@bound_var = node_value
|
41
|
+
@body = child1
|
42
|
+
when 3
|
43
|
+
@kind = :application
|
44
|
+
@function = child1
|
45
|
+
@argument = child2
|
46
|
+
else raise ArgumentError, "Wrong number of arguments. (Should be caught earlier.)"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
31
50
|
|
51
|
+
def sanitize_args(*args)
|
32
52
|
args.compact!
|
33
53
|
|
34
|
-
# If the expression is initialized in natural mode, as a written-out string, the
|
54
|
+
# If the expression is initialized in natural mode, as a written-out string, the sanitizer passes that string to .string_to_lambda_args to turn it into the native arguments.
|
35
55
|
if args.length == 1 && args[0].is_a?(String)
|
36
56
|
args = LambdaExpression.string_to_lambda_args(args[0])
|
37
57
|
end
|
@@ -42,7 +62,7 @@ class LambdaExpression
|
|
42
62
|
end
|
43
63
|
|
44
64
|
if node_value.is_a?(String)
|
45
|
-
node_value.to_sym
|
65
|
+
node_value = node_value.to_sym
|
46
66
|
end
|
47
67
|
|
48
68
|
if node_value.is_a?(Symbol)
|
@@ -58,40 +78,24 @@ class LambdaExpression
|
|
58
78
|
else raise ArgumentError, "First argument is not a symbol."
|
59
79
|
end
|
60
80
|
|
61
|
-
|
62
|
-
|
63
|
-
case args.length
|
64
|
-
when 1
|
65
|
-
@kind = :variable
|
66
|
-
@value = node_value
|
67
|
-
when 2
|
68
|
-
@kind = :abstraction
|
69
|
-
@bound_var = node_value
|
70
|
-
@body = child1
|
71
|
-
when 3
|
72
|
-
@kind = :application
|
73
|
-
@function = child1
|
74
|
-
@argument = child2
|
75
|
-
else raise ArgumentError, "Wrong number of arguments. (Should be caught earlier.)"
|
76
|
-
end
|
77
|
-
|
81
|
+
return *[node_value, child1, child2].compact
|
78
82
|
end
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
argument_string = "#{self.argument}"
|
88
|
-
if self.function.kind == :abstraction
|
89
|
-
function_string = "(" + function_string + ")"
|
84
|
+
def self.string_to_lambda_args(string)
|
85
|
+
array = group_by_parens(string)
|
86
|
+
if array.length == 1
|
87
|
+
string = array[0]
|
88
|
+
if string[0] == '\\'
|
89
|
+
return string[1].to_sym, string[3..-1]
|
90
|
+
else return string.to_sym # Symbols are required to be only one character; this is enforced later in the initializer.
|
90
91
|
end
|
91
|
-
|
92
|
-
|
92
|
+
else
|
93
|
+
last = array.pop
|
94
|
+
array.map! { |element| LambdaExpression.new(element) }
|
95
|
+
penultimate = array.inject do |function, next_arg|
|
96
|
+
LambdaExpression.new(:*, function, next_arg)
|
93
97
|
end
|
94
|
-
return
|
98
|
+
return :*, penultimate, last
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
@@ -115,25 +119,26 @@ class LambdaExpression
|
|
115
119
|
array
|
116
120
|
end
|
117
121
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
122
|
+
def to_s
|
123
|
+
case self.instance_variable_get(:@kind)
|
124
|
+
when :variable then self.value.to_s
|
125
|
+
when :abstraction then "\\#{self.bound_var}.#{self.body}"
|
126
|
+
when :application
|
127
|
+
function_string = "#{self.function}"
|
128
|
+
argument_string = "#{self.argument}"
|
129
|
+
if self.function.kind == :abstraction
|
130
|
+
function_string = "(" + function_string + ")"
|
125
131
|
end
|
126
|
-
|
127
|
-
|
128
|
-
array.map! { |element| LambdaExpression.new(element) }
|
129
|
-
penultimate = array.inject do |function, next_arg|
|
130
|
-
LambdaExpression.new(:*, function, next_arg)
|
132
|
+
unless self.argument.kind == :variable
|
133
|
+
argument_string = "(" + argument_string + ")"
|
131
134
|
end
|
132
|
-
return
|
135
|
+
return function_string + argument_string
|
136
|
+
else raise "Lambda expression has no kind."
|
133
137
|
end
|
134
138
|
end
|
135
139
|
|
136
140
|
# I stole this from the iternet, but understand how it works and tested it.
|
141
|
+
# ----- Change this so that it doesn't have any new instance variables.
|
137
142
|
def deep_clone
|
138
143
|
return @deep_cloning_obj if @deep_cloning
|
139
144
|
@deep_cloning_obj = clone
|
@@ -154,12 +159,34 @@ class LambdaExpression
|
|
154
159
|
deep_cloning_obj
|
155
160
|
end
|
156
161
|
|
157
|
-
#
|
162
|
+
# Deep equality; tests whether LambdaExpressions are the same down to the value of the instance variables.
|
163
|
+
def ===(other)
|
164
|
+
return true if self == other
|
165
|
+
if self.kind == other.kind
|
166
|
+
case self.kind
|
167
|
+
when :variable
|
168
|
+
return self.value == other.value
|
169
|
+
when :abstraction
|
170
|
+
return (self.bound_var == other.bound_var &&
|
171
|
+
self.body === other.body)
|
172
|
+
when :application
|
173
|
+
return (self.function === other.function &&
|
174
|
+
self.argument === other.argument)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
|
180
|
+
# After de Bruijn indices are implemented, this will be the same as deep equality.
|
181
|
+
def alpha_equal?(other)
|
182
|
+
end
|
183
|
+
|
184
|
+
# This method does beta reduction only at the top level. Fuller lambda expression evaluation is done by #evaluate.
|
158
185
|
def beta_reduce
|
159
|
-
|
160
|
-
|
161
|
-
return copy
|
186
|
+
unless (self.kind == :application) && (self.function.kind == :abstraction)
|
187
|
+
return self
|
162
188
|
end
|
189
|
+
copy = self.deep_clone
|
163
190
|
replacement = copy.argument
|
164
191
|
bound_variable = copy.function.bound_var
|
165
192
|
|
@@ -178,30 +205,51 @@ class LambdaExpression
|
|
178
205
|
self.function = self.function.substitute(bound_variable, replacement)
|
179
206
|
self.argument = self.argument.substitute(bound_variable, replacement)
|
180
207
|
return self
|
181
|
-
else raise ArgumentError, "First argument is not a LambdaExpression or has no
|
208
|
+
else raise ArgumentError, "First argument is not a LambdaExpression or has no kind."
|
182
209
|
end
|
183
210
|
end
|
184
211
|
|
185
212
|
copy.function.body.substitute(bound_variable, replacement)
|
186
213
|
end
|
187
214
|
|
188
|
-
def evaluate(strategy=:
|
189
|
-
|
215
|
+
def evaluate(strategy=:depth_first)
|
216
|
+
case strategy
|
217
|
+
when :depth_first then self.evaluate_depth_first
|
218
|
+
else raise ArgumentError, "Argument #{strategy} is not an evaluation strategy."
|
219
|
+
end
|
220
|
+
# Eager/strict, pre-order, in-order, post-order/applicative-order, breadth-first, unstoppable hybrid
|
221
|
+
end
|
222
|
+
|
223
|
+
def evaluate_depth_first
|
224
|
+
expression = self.deep_clone
|
225
|
+
case expression.kind
|
226
|
+
when :variable then return expression
|
227
|
+
when :abstraction then return LambdaExpression.new( expression.bound_var, expression.body.evaluate_depth_first )
|
228
|
+
when :application
|
229
|
+
expression.function = expression.function.evaluate_depth_first
|
230
|
+
if expression.function.kind == :abstraction
|
231
|
+
return expression.beta_reduce.evaluate_depth_first
|
232
|
+
else
|
233
|
+
expression.argument = expression.argument.evaluate_depth_first
|
234
|
+
return expression
|
235
|
+
end
|
236
|
+
else raise ArgumentError, "This lambda expression has a wrong kind."
|
237
|
+
end
|
190
238
|
end
|
191
239
|
|
192
240
|
# This method helps me test LambdaExpression objects.
|
193
241
|
def lambda_tester
|
194
|
-
puts "To string:
|
195
|
-
puts "kind:
|
242
|
+
puts "To string: #{self}"
|
243
|
+
puts "kind: #{self.kind}"
|
196
244
|
case self.kind
|
197
245
|
when :variable
|
198
|
-
puts "value:
|
246
|
+
puts "value: #{self.value}"
|
199
247
|
when :abstraction
|
200
|
-
puts "bound_var:
|
201
|
-
puts "body:
|
248
|
+
puts "bound_var: #{self.bound_var}"
|
249
|
+
puts "body: #{self.body}"
|
202
250
|
when :application
|
203
|
-
puts "function:
|
204
|
-
puts "argument:
|
251
|
+
puts "function: #{self.function}"
|
252
|
+
puts "argument: #{self.argument}"
|
205
253
|
end
|
206
254
|
end
|
207
255
|
|
@@ -210,28 +258,27 @@ class LambdaExpression
|
|
210
258
|
puts "Test string: #{string}"
|
211
259
|
puts "Paren grouping: #{LambdaExpression.group_by_parens(string)}"
|
212
260
|
args = LambdaExpression.string_to_lambda_args(string)
|
213
|
-
arg0, arg1, arg2 = args
|
214
261
|
puts "Lambda arguments: #{args}"
|
215
|
-
thing = LambdaExpression.new(
|
262
|
+
thing = LambdaExpression.new(*args)
|
216
263
|
thing.lambda_tester
|
217
264
|
end
|
218
265
|
|
219
266
|
# Runs a block on every leaf in the tree.
|
220
267
|
def each_leaf!
|
221
|
-
raise
|
268
|
+
raise "Method not yet written."
|
222
269
|
|
223
270
|
self.each do |leaf|
|
224
271
|
yield(leaf)
|
225
272
|
end
|
226
273
|
end
|
227
274
|
|
275
|
+
def evaluates_to?(other, strategy=:depth_first)
|
276
|
+
self.evaluate(strategy) === other
|
277
|
+
end
|
278
|
+
|
228
279
|
# Returns a random LambdaExpression
|
229
280
|
def self.random
|
230
281
|
raise "Method not yet written."
|
231
282
|
end
|
232
283
|
|
233
284
|
end
|
234
|
-
|
235
|
-
# LambdaExpression.lambda_string_tester('(\x.yx)\a.bb')
|
236
|
-
# p test.beta_reduce
|
237
|
-
# p test
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe LambdaExpression do
|
4
|
+
|
5
|
+
describe 'new' do
|
6
|
+
|
7
|
+
it "should fail to initizlie the empty string" do
|
8
|
+
expect { LambdaExpression.new('') }.to raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should initialize single characters as variables" do
|
12
|
+
test_cases = ['x', :a, 'g', :l, 'y', 'z']
|
13
|
+
test_cases.each do |expression|
|
14
|
+
LambdaExpression.new(expression).kind.should == :variable
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should initialize abstractions" do
|
19
|
+
test_cases = [[:x, :x] , [:a, 'xa'], ['g', 'ga'], ['x', :x], ['\x.x'], ['\x.\y.xy']]
|
20
|
+
test_cases.each do |expression|
|
21
|
+
LambdaExpression.new(*expression).kind.should == :abstraction
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should initialize applications" do
|
26
|
+
test_cases = [[:*, :x, :x], ['*', :a, 'x'], ['*', 'g', 'a'], ['xx'], ['a\x.\y.xy']]
|
27
|
+
test_cases.each do |expression|
|
28
|
+
LambdaExpression.new(*expression).kind.should == :application
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should reject * as an argument in all other cases" do
|
33
|
+
test_cases = [[:*], [:*, :*], [:*, '*'], ['*', '*'], ['*', :*], ['x', :*], [:x, :*], [:x, '*'], ['x', '*'], ['*', '*', 'x'], ['*', 'x', '*'], [:*, :*, :x], ['a', '*', '*']]
|
34
|
+
test_cases.each do |expression|
|
35
|
+
expect { LambdaExpression.new(*expression) }.to raise_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'beta_reduce' do
|
42
|
+
|
43
|
+
it "should return the same object if it can't reduce" do
|
44
|
+
example = LambdaExpression.new('x')
|
45
|
+
example.beta_reduce.should == example
|
46
|
+
example.beta_reduce.value.should == example.value # Dubious meaning.
|
47
|
+
|
48
|
+
test_cases = ['\x.x', 'xy', 'x\y.y', 'xyz', '(\a.aab)xy']
|
49
|
+
test_cases.each do |expression|
|
50
|
+
example = LambdaExpression.new(expression)
|
51
|
+
example.beta_reduce.should == example
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return a different object if it can perform beta reduction" do
|
56
|
+
test_cases = ['(\x.xx)(\y.yy)', '(\x.xx)(\x.xx)', '(\x.yx)a', '(\a.aab)(xy)']
|
57
|
+
test_cases.each do |expression|
|
58
|
+
example = LambdaExpression.new(expression)
|
59
|
+
example.beta_reduce.should_not == example
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'evaluate' do
|
66
|
+
it "should return the same object for expressions that can't be evaluated"
|
67
|
+
|
68
|
+
# I have no idea how to correctly implement this.
|
69
|
+
# it "should return with any strategy for expressions with finite reduction steps" do
|
70
|
+
# test_cases = ['x', 'xy'] # Come up with complicated examples.
|
71
|
+
# test_cases.each do |string|
|
72
|
+
# expression = LambdaExpression.new(string)
|
73
|
+
# LambdaExpression.strategies.each do |strategy|
|
74
|
+
# expression.evaluate(strategy).should # ... should what?
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '===' do
|
81
|
+
|
82
|
+
it "should return true when same up to same variable names, false otherwise" do
|
83
|
+
test_cases = { 'x'=>'x', :x=>'x', 'xy'=>'xy', '\a.ba'=>'\a.ba', '(\x.yx)a'=>'(\x.yx)a' }
|
84
|
+
test_cases.each do |expression|
|
85
|
+
example_self, example_other = LambdaExpression.new(expression[0]), LambdaExpression.new(expression[1])
|
86
|
+
example_self.should === example_other
|
87
|
+
end
|
88
|
+
|
89
|
+
test_cases = { 'x'=>'y', :x=>'y', 'xy'=>'yx', '\b.ba'=>'\a.ab', '(\x.yx)b'=>'(\x.yx)a' }
|
90
|
+
test_cases.each do |expression|
|
91
|
+
example_self, example_other = LambdaExpression.new(expression[0]), LambdaExpression.new(expression[1])
|
92
|
+
example_self.should_not === example_other
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should be reflexive" do
|
97
|
+
test_cases = ['x', :x, 'xy', '\a.ba', '(\x.yx)a']
|
98
|
+
test_cases.each do |expression|
|
99
|
+
example = LambdaExpression.new(expression)
|
100
|
+
example.should === example
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be symmetrical" do
|
105
|
+
test_cases = { 'x'=>'x', :x=>'x', 'xy'=>'xy', '\a.ba'=>'\a.ba', '(\x.yx)a'=>'(\x.yx)a', 'x'=>'y', :x=>'y', 'xy'=>'yx', '\b.ba'=>'\a.ab', '(\x.yx)b'=>'(\x.yx)a' }
|
106
|
+
test_cases.each do |expression|
|
107
|
+
example_self, example_other = LambdaExpression.new(expression[0]), LambdaExpression.new(expression[1])
|
108
|
+
(example_self === example_other).should == (example_other === example_self)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Later, too hard.
|
113
|
+
# it "should be transitive" do
|
114
|
+
# end
|
115
|
+
|
116
|
+
# pending "write a pending message"
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'deep_clone' do
|
121
|
+
it "should copy complex expressions which are alpha_equal and === but not =="
|
122
|
+
it "should leave the self untouched"
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'alpha_equal?' do
|
126
|
+
it "should return true for expressions with the same structure but different variable names"
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
# Can I write a function, has_lambda_properties?, to use in this file?
|
132
|
+
# Edge cases
|
133
|
+
# The base cases create objects with correct properties
|
134
|
+
# Make complex specific LambdaExpressions and make sure they were initialized correctly
|
135
|
+
# Beta reduce leaves self untouched
|
136
|
+
# RSpec with random lambdas
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
# test_cases = [blah]
|
141
|
+
# test_cases.each do |expression|
|
142
|
+
# # code
|
143
|
+
# end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,27 +1,51 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lambda-calculus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Altair
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
11
|
+
date: 2013-10-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: This gem allows users to create and evaluate lambda expression objects
|
28
|
+
from natural string input like '(\x.xy)(\a.aba)'.
|
15
29
|
email: alexanderaltair@gmail.com
|
16
30
|
executables: []
|
17
31
|
extensions: []
|
18
32
|
extra_rdoc_files: []
|
19
33
|
files:
|
34
|
+
- .rspec
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lambda-calculus.gemspec
|
20
40
|
- lib/lambda-calculus.rb
|
41
|
+
- spec/lambda_calculus_spec.rb
|
42
|
+
- spec/spec_helper.rb
|
21
43
|
homepage: https://github.com/alexaltair/lambda-calculus
|
22
|
-
licenses:
|
44
|
+
licenses:
|
45
|
+
- MIT
|
23
46
|
metadata: {}
|
24
|
-
post_install_message:
|
47
|
+
post_install_message: ! '* Check out the documentation at https://github.com/alexaltair/lambda-calculus
|
48
|
+
*'
|
25
49
|
rdoc_options: []
|
26
50
|
require_paths:
|
27
51
|
- lib
|
@@ -41,4 +65,6 @@ rubygems_version: 2.0.5
|
|
41
65
|
signing_key:
|
42
66
|
specification_version: 4
|
43
67
|
summary: A class with methods for creating and evaluating lambda expressions.
|
44
|
-
test_files:
|
68
|
+
test_files:
|
69
|
+
- spec/lambda_calculus_spec.rb
|
70
|
+
- spec/spec_helper.rb
|