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.
Files changed (3) hide show
  1. checksums.yaml +15 -0
  2. data/lib/lambda-calculus.rb +237 -0
  3. 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: []