lambda-calculus 0.1.0 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/lambda-calculus.png)](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
|