lambda-calculus 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.
- checksums.yaml +15 -0
- data/lib/lambda-calculus.rb +237 -0
- metadata +44 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZGZmYzJlZTA5MjY5ZGViOTEzY2VhZGI4NGE0OWMxNTRlM2U1NTc5YQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTlmMzZiNzk1MDk4YjNmZWY5NjhkYTRmMGM3OWJmMzJjNDY2ZGI3YQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MWI5MzM2MGYwNjRkMWI3Zjc1ODNmNjFhNGM1Zjc0YmQwZmE3ZDRjY2EwMGQ4
|
10
|
+
MmNhZWVhM2MzYTQzNGQxNDdjMjVlOTc5OTI2ZmJhYjcwZWU2MGU5MDgwNDg2
|
11
|
+
ZTdjOGI1NTNlNzU0MzNkOGVlZjRjZjAwN2I0ZDcxMGNkMDExZjg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGZiY2U3MjEyYzRmZWRmMjA4NWUxMDY4NDhjYjlmYWE0YTMzYzNiOGY4MWEy
|
14
|
+
ODI2MDNiOGI5M2JjZDljNDk1NGM2YjU0ODU0NWYwMDRjMzNkNmJlOTE4ZmQ3
|
15
|
+
MzkxN2JlNmU1ZmM3MDM0NTYzYWUyZTU2OWY3NDI3ZDRjNWFkMmY=
|
@@ -0,0 +1,237 @@
|
|
1
|
+
# Takes a string and find the index of the matching close paren to the open paren specified. Default open paren index is the beginning of the string.
|
2
|
+
def close_paren_index(string, open_paren_index = 0)
|
3
|
+
if open_paren_index == 0
|
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] == '('
|
7
|
+
end
|
8
|
+
array = string.split('')
|
9
|
+
depth = 1
|
10
|
+
start = open_paren_index + 1
|
11
|
+
array.shift(start)
|
12
|
+
array.each_with_index do |char, index|
|
13
|
+
if char == ')'
|
14
|
+
depth -= 1
|
15
|
+
return index + start if depth == 0
|
16
|
+
elsif char == '('
|
17
|
+
depth += 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
raise "Mismatching parentheses."
|
21
|
+
end
|
22
|
+
|
23
|
+
class LambdaExpression
|
24
|
+
|
25
|
+
# Change the setters so that attributes cannot be changed to make a meaningless lambda expression.
|
26
|
+
attr_accessor :kind, :value, :bound_var, :body, :function, :argument
|
27
|
+
|
28
|
+
def initialize(*args)
|
29
|
+
|
30
|
+
# The first half of this deals with all possible types of arguments that could be given.
|
31
|
+
|
32
|
+
args.compact!
|
33
|
+
|
34
|
+
# If the expression is initialized in natural mode, as a written-out string, the initializer passes that string to .string_to_lambda_args to turn it into the native arguments.
|
35
|
+
if args.length == 1 && args[0].is_a?(String)
|
36
|
+
args = LambdaExpression.string_to_lambda_args(args[0])
|
37
|
+
end
|
38
|
+
|
39
|
+
if args.length <= 3
|
40
|
+
node_value, child1, child2 = args
|
41
|
+
else raise ArgumentError, "Number of arguments must be 3 or less."
|
42
|
+
end
|
43
|
+
|
44
|
+
if node_value.is_a?(String)
|
45
|
+
node_value.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
if node_value.is_a?(Symbol)
|
49
|
+
raise ArgumentError, "Literal variables should be only one character. Passing an entire expression as a string should take no other arguments." unless node_value.length == 1
|
50
|
+
if node_value == :*
|
51
|
+
raise ArgumentError, "Application needs three arguments." unless args.length == 3
|
52
|
+
child1 = LambdaExpression.new(child1) unless child1.is_a?(LambdaExpression)
|
53
|
+
child2 = LambdaExpression.new(child2) unless child2.is_a?(LambdaExpression)
|
54
|
+
elsif args.length == 2
|
55
|
+
child1 = LambdaExpression.new(child1) unless child1.is_a?(LambdaExpression)
|
56
|
+
elsif args.length == 3 then raise ArgumentError, "First argument should be :*, or too many arguments."
|
57
|
+
end # No else here, because we want the remaining cases of args.length == 1 to drop down into the case statement to become :variable LambdaExpressions!
|
58
|
+
else raise ArgumentError, "First argument is not a symbol."
|
59
|
+
end
|
60
|
+
|
61
|
+
# At this point the first argument should be a symbol, and the rest LambdaExpressions.
|
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
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
case self.kind
|
83
|
+
when :variable then self.value.to_s
|
84
|
+
when :abstraction then "\\#{self.bound_var}.#{self.body}"
|
85
|
+
when :application
|
86
|
+
function_string = "#{self.function}"
|
87
|
+
argument_string = "#{self.argument}"
|
88
|
+
if self.function.kind == :abstraction
|
89
|
+
function_string = "(" + function_string + ")"
|
90
|
+
end
|
91
|
+
unless self.argument.kind == :variable
|
92
|
+
argument_string = "(" + argument_string + ")"
|
93
|
+
end
|
94
|
+
return function_string + argument_string
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Doesn't take care of double parens '((a))' or empty parens 'a()'
|
99
|
+
def self.group_by_parens(string)
|
100
|
+
array = []
|
101
|
+
while string.length > 0
|
102
|
+
if string[0] == '('
|
103
|
+
paren = close_paren_index(string)
|
104
|
+
array << string[1...paren]
|
105
|
+
string = string[(paren+1)..-1]
|
106
|
+
elsif string[0] == '\\'
|
107
|
+
array << string
|
108
|
+
string = ''
|
109
|
+
else
|
110
|
+
array << string[0]
|
111
|
+
string = string[1..-1]
|
112
|
+
# This implies that varaibles cannot be longer than one character.
|
113
|
+
end
|
114
|
+
end
|
115
|
+
array
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.string_to_lambda_args(string)
|
119
|
+
array = group_by_parens(string)
|
120
|
+
if array.length == 1
|
121
|
+
string = array[0]
|
122
|
+
if string[0] == '\\'
|
123
|
+
return string[1].to_sym, string[3..-1]
|
124
|
+
else return string.to_sym # Symbols are required to be only one character; this is enforced later in the initializer.
|
125
|
+
end
|
126
|
+
else
|
127
|
+
last = array.pop
|
128
|
+
array.map! { |element| LambdaExpression.new(element) }
|
129
|
+
penultimate = array.inject do |function, next_arg|
|
130
|
+
LambdaExpression.new(:*, function, next_arg)
|
131
|
+
end
|
132
|
+
return :*, penultimate, last
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# I stole this from the iternet, but understand how it works and tested it.
|
137
|
+
def deep_clone
|
138
|
+
return @deep_cloning_obj if @deep_cloning
|
139
|
+
@deep_cloning_obj = clone
|
140
|
+
@deep_cloning_obj.instance_variables.each do |var|
|
141
|
+
val = @deep_cloning_obj.instance_variable_get(var)
|
142
|
+
begin
|
143
|
+
@deep_cloning = true
|
144
|
+
val = val.deep_clone
|
145
|
+
rescue TypeError, NoMethodError
|
146
|
+
next
|
147
|
+
ensure
|
148
|
+
@deep_cloning = false
|
149
|
+
end
|
150
|
+
@deep_cloning_obj.instance_variable_set(var, val)
|
151
|
+
end
|
152
|
+
deep_cloning_obj = @deep_cloning_obj
|
153
|
+
@deep_cloning_obj = nil
|
154
|
+
deep_cloning_obj
|
155
|
+
end
|
156
|
+
|
157
|
+
# This method does beta reduction only at the top level. Fuller lambda experession evaluation is done by evaluate.
|
158
|
+
def beta_reduce
|
159
|
+
copy = self.deep_clone
|
160
|
+
unless (copy.kind == :application) && (copy.function.kind == :abstraction)
|
161
|
+
return copy
|
162
|
+
end
|
163
|
+
replacement = copy.argument
|
164
|
+
bound_variable = copy.function.bound_var
|
165
|
+
|
166
|
+
# This method modifies self, which doesn't matter while it's inside beta_reduce, because a deep copy is made first.
|
167
|
+
def substitute(bound_variable, replacement)
|
168
|
+
if self.kind == :variable
|
169
|
+
if self.value == bound_variable
|
170
|
+
return replacement
|
171
|
+
else
|
172
|
+
return self
|
173
|
+
end
|
174
|
+
elsif self.kind == :abstraction
|
175
|
+
self.body = self.body.substitute(bound_variable, replacement)
|
176
|
+
return self
|
177
|
+
elsif self.kind == :application
|
178
|
+
self.function = self.function.substitute(bound_variable, replacement)
|
179
|
+
self.argument = self.argument.substitute(bound_variable, replacement)
|
180
|
+
return self
|
181
|
+
else raise ArgumentError, "First argument is not a LambdaExpression or has no .kind."
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
copy.function.body.substitute(bound_variable, replacement)
|
186
|
+
end
|
187
|
+
|
188
|
+
def evaluate(strategy=:lazy)
|
189
|
+
raise "Method not yet written."
|
190
|
+
end
|
191
|
+
|
192
|
+
# This method helps me test LambdaExpression objects.
|
193
|
+
def lambda_tester
|
194
|
+
puts "To string: #{self}"
|
195
|
+
puts "kind: #{self.kind}"
|
196
|
+
case self.kind
|
197
|
+
when :variable
|
198
|
+
puts "value: #{self.value}"
|
199
|
+
when :abstraction
|
200
|
+
puts "bound_var: #{self.bound_var}"
|
201
|
+
puts "body: #{self.body}"
|
202
|
+
when :application
|
203
|
+
puts "function: #{self.function}"
|
204
|
+
puts "argument: #{self.argument}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# This method helps me test LambdaExpression objects created from strings.
|
209
|
+
def self.lambda_string_tester(string)
|
210
|
+
puts "Test string: #{string}"
|
211
|
+
puts "Paren grouping: #{LambdaExpression.group_by_parens(string)}"
|
212
|
+
args = LambdaExpression.string_to_lambda_args(string)
|
213
|
+
arg0, arg1, arg2 = args
|
214
|
+
puts "Lambda arguments: #{args}"
|
215
|
+
thing = LambdaExpression.new(arg0, arg1, arg2)
|
216
|
+
thing.lambda_tester
|
217
|
+
end
|
218
|
+
|
219
|
+
# Runs a block on every leaf in the tree.
|
220
|
+
def each_leaf!
|
221
|
+
raise 'Method not yet written.'
|
222
|
+
|
223
|
+
self.each do |leaf|
|
224
|
+
yield(leaf)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Returns a random LambdaExpression
|
229
|
+
def self.random
|
230
|
+
raise "Method not yet written."
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
# LambdaExpression.lambda_string_tester('(\x.yx)\a.bb')
|
236
|
+
# p test.beta_reduce
|
237
|
+
# p test
|
metadata
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lambda-calculus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Altair
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This gem allows users to create lambda expression objects in Ruby from
|
14
|
+
natural string input like '(\x.xy)(\a.aba)'.
|
15
|
+
email: alexanderaltair@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/lambda-calculus.rb
|
21
|
+
homepage: https://github.com/alexaltair/lambda-calculus
|
22
|
+
licenses: []
|
23
|
+
metadata: {}
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
requirements: []
|
39
|
+
rubyforge_project:
|
40
|
+
rubygems_version: 2.0.5
|
41
|
+
signing_key:
|
42
|
+
specification_version: 4
|
43
|
+
summary: A class with methods for creating and evaluating lambda expressions.
|
44
|
+
test_files: []
|