functional-ruby 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1033510698e79e8472d8fee1bc631b9d03edce14
4
- data.tar.gz: f3fbc19a3c18543984cba90b64ac8508b105bd63
3
+ metadata.gz: 97e8113fb43fd8fda7f581f0e3bc342e66b42cdb
4
+ data.tar.gz: c64e9d6a0788814d50bdc87b00ca7d00caa887a5
5
5
  SHA512:
6
- metadata.gz: 4d206b9ddcc7ab49f2225e8fe9a3fb638d9b35ab09a75ee6a33602d000003cf235dab1650fcc8e72accfaed3371b7254104bbae4227db16c64eaf4ea15cacb37
7
- data.tar.gz: d90c081ad4efc3963d4e125d9c1867c138067d096e136c0fb0ad9d124f8438415d098a36847e877ccf9186df81906f495cecebcff55bdde7885dfb2af403a0e2
6
+ metadata.gz: ac1e57dac94eedaf8a94a45715ecc5757238afa8a7fe4dc24e57b3255d1ce7f1e64a3b2d562c099d0b77ca1b815778905ad561c09e46c337c1b43c0e4c4188f2
7
+ data.tar.gz: 4db984862cf3b281ed4251e4fd1d6f8702e015f47a348d091080f84df0479cdc3fa77ecc2eafb0c64801c7e7d8a58ca8d7a3f1dd8ef7e1006359f02e498f94d6
data/README.md CHANGED
@@ -16,21 +16,50 @@ The project is hosted on the following sites:
16
16
 
17
17
  ## Introduction
18
18
 
19
- Three things I love are [Ruby](http://www.ruby-lang.org/en/),
19
+ Two things I love are [Ruby](http://www.ruby-lang.org/en/) and
20
20
  [functional](https://en.wikipedia.org/wiki/Functional_programming)
21
- [programming](http://c2.com/cgi/wiki?FunctionalProgramming) and
22
- [concurrency](http://www.amazon.com/s/ref=nb_sb_noss_1?url=search-alias%3Dstripbooks&field-keywords=concurrent%20programming).
23
- Sadly, the first is generally not associated with the other two. First, I reject the
24
- assertion that Ruby is an object-oriented language. It's certainly object-based, since
21
+ [programming](http://c2.com/cgi/wiki?FunctionalProgramming).
22
+ Sadly, the former is generally not associated with the latter. Unfortunately,
23
+ too many people are blinded by their belief that Ruby is an object-oriented
24
+ language. I reject this assertion. Ruby is certainly object-based, since
25
25
  everything is an object, but entire large-scale programs can be built without ever
26
- defining a single class. Ruby is a true multi-paradigm language and easily supports
27
- many advanced functional techniques. As to concurrency, Ruby's bad reputation is
28
- well earned, but recent versions of Ruby have made significan improvements in that
29
- area. Ruby 2.0 is now a [relevant](https://blog.heroku.com/archives/2013/6/17/ruby-2-default-new-aps)
30
- platform for concurrent applications.
26
+ defining a single class. But Ruby's features that support functional programming
27
+ don't stop there.
28
+
29
+ Ask ten different programmers to define the term "functional programming" and
30
+ you will likely get ten different answers. One characteristic that will certainly
31
+ be on all their lists is support for
32
+ [higher](http://en.wikipedia.org/wiki/Higher-order_function)
33
+ [order](http://learnyouahaskell.com/higher-order-functions)
34
+ [functions](http://learnyousomeerlang.com/higher-order-functions). Put simply, a
35
+ higher order function is any function that can take one or more functions as
36
+ parameters and/or return a function as a result. Many functional languages,
37
+ such as Erlang, Haskell, and Closure, support higher order functions. Higher order
38
+ functions are a remarkable tool that can completely change the was software is
39
+ designed. Most classicaly object-oriented languages do not support higher
40
+ order functions. Unfortunately, Ruby does not directly support higher order
41
+ functions. Thanksfully, Ruby *does* give us blocks, `proc`s, and `lambda`s.
42
+ Though not strictly higher order functions, in most cases they are functionally
43
+ equivalent.
44
+
45
+ If you combine Ruby's ability to create functions sans-classes with the power
46
+ of blocks/`proc`s/`lambda`s, Ruby code can follow just about every modern functional
47
+ programming design paradigm. Hence, I consider Ruby to be a *multi-paradigm* language.
48
+ Add to this Ruby's vast metaprogramming capabilities and Ruby is easily one of the
49
+ most powerful languages in common use today.
31
50
 
32
51
  This gem is my small and humble attempt to help Ruby reach its full potential as
33
- a highly performant, functional programming language.
52
+ a highly performant, functional programming language. Virtually every function in
53
+ this library takes a block parameter. Some allow a block plus one or more `proc`
54
+ arguments. Most operate *on* data structures rather than being buried *in* data
55
+ structures. Finally, several of the tools in this library are Ruby implementations
56
+ of some of my favorite features from other functional programming languages. Not
57
+ every function is pure, but functions with side effects are easy to spot because
58
+ they almost always have names that end in an exclamation point.
59
+
60
+ My hope is that this gem will help Ruby programmers explore Ruby as a functional
61
+ language and improve their code in ways our object oriented brethern never
62
+ dreamed possible.
34
63
 
35
64
  ### Goals
36
65
 
@@ -48,8 +77,13 @@ My goal is to implement various functional programming patterns in Ruby. Specifi
48
77
 
49
78
  Several features from Erlang, Go, and Clojure have been implemented thus far:
50
79
 
51
- * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
52
80
  * Interface specifications with Erlang-style [Behavior](https://github.com/jdantonio/functional-ruby/blob/master/md/behavior.md)
81
+ * A [Catalog](https://github.com/jdantonio/functional-ruby/blob/master/md/catalog.md) class for managing sets of key/value pairs in a manner similar to Erlang's [proplists](http://www.erlang.org/doc/man/proplists.html)
82
+ * A toolkit of [collection](https://github.com/jdantonio/functional-ruby/blob/master/md/collection.md) utilities for operating on list-like data structures
83
+ * A set of string [inflections](https://github.com/jdantonio/functional-ruby/blob/master/md/inflect.md) borrowed from [Active Support](http://guides.rubyonrails.org/active_support_core_extensions.html#inflections)
84
+ * Function overloading with Erlang-style [Pattern Matching](https://github.com/jdantonio/functional-ruby/blob/master/md/pattern_matching.md)
85
+ * Tools for introspecting the runtime [Platform](https://github.com/jdantonio/functional-ruby/blob/master/md/platform.md) for information about the operating system and Ruby version
86
+ * [Search](https://github.com/jdantonio/functional-ruby/blob/master/md/search.md) and [sort](https://github.com/jdantonio/functional-ruby/blob/master/md/sort.md) algorithms like you remember from your algorithms class, but with a functional twist
53
87
  * Several useful functional [Utilities](https://github.com/jdantonio/functional-ruby/blob/master/md/utilities.md)
54
88
 
55
89
  ### Is it any good?
@@ -1,5 +1,24 @@
1
1
  require 'functional/behavior'
2
+ require 'functional/behaviour'
3
+ require 'functional/catalog'
4
+ require 'functional/collection'
5
+ require 'functional/inflect'
2
6
  require 'functional/pattern_matching'
3
7
  require 'functional/platform'
8
+ require 'functional/search'
9
+ require 'functional/sort'
4
10
  require 'functional/utilities'
5
11
  require 'functional/version'
12
+
13
+ Infinity = 1/0.0 unless defined?(Infinity)
14
+ NaN = 0/0.0 unless defined?(NaN)
15
+
16
+ module Functional
17
+
18
+ class << self
19
+ include Collection
20
+ include Inflect
21
+ include Search
22
+ include Sort
23
+ end
24
+ end
@@ -0,0 +1,487 @@
1
+ module Functional
2
+
3
+ # A collection of key/value pairs similar to a hash but ordered.
4
+ # Access is via index (like an array) rather than by key (like a
5
+ # hash). Supports duplicate keys. Indexing starts at zero.
6
+ class Catalog
7
+ include Enumerable
8
+
9
+ # Create a new Catalog from the given data. When +data+ is nil
10
+ # or an empty collection the resulting Catalog will be empty.
11
+ # When +data+ is an array, hash, or catalog array the appropriate
12
+ # +#from_+ factory method will be called. The +:from+ option is
13
+ # used to indicate the type of the source data.
14
+ #
15
+ # If a block is given each value in the from the source array will
16
+ # be passed to the block and the result will be stored as the value
17
+ # in the Catalog.
18
+ #
19
+ # @param [Array, Hash, Catalog] data the data to construct the
20
+ # Catalog from
21
+ # @param [Hash] opts processing options
22
+ #
23
+ # @option opts [Symbol] :from the type of the data source. Valid values
24
+ # are :catalog/:catalogue, :hash, :array (default :catalog).
25
+ def initialize(data=nil, opts={})
26
+
27
+ if block_given?
28
+
29
+ @data = []
30
+ data.each do |item|
31
+ @data << yield(item)
32
+ end
33
+
34
+ else
35
+ from = opts[:from]
36
+ from = :array if [:set, :list, :stack, :queue, :vector].include?(from)
37
+ from = "from_#{from}".to_sym
38
+
39
+ if Catalog.respond_to?(from)
40
+ @data = Catalog.send(from, data)
41
+ @data = @data.instance_variable_get(:@data)
42
+ elsif opts[:from].nil? && !data.nil?
43
+ @data = Catalog.from_catalog(data)
44
+ @data = @data.instance_variable_get(:@data)
45
+ else
46
+ @data = []
47
+ end
48
+ end
49
+ end
50
+
51
+ # Creates a new Catalog object from a hash. Each key/value pair in the
52
+ # hash will be converted to a key/value array in the new Catalog. If a
53
+ # block is given each value in the array will be passed to the block
54
+ # and the result will be stored as the value in the Catalog.
55
+ def self.from_hash(data = {})
56
+ collected = []
57
+ data.each do |key, value|
58
+ value = yield(value) if block_given?
59
+ collected << [key, value]
60
+ end
61
+ catalog = Catalog.new
62
+ catalog.instance_variable_set(:@data, collected)
63
+ return catalog
64
+ end
65
+
66
+ # Creates a new catalog object from an array. Each successive pair of
67
+ # elements will become a key/value pair in the new Catalog. If the source
68
+ # array has an odd number of elements the last element will be discarded.
69
+ # If a block is given each element in the source array will be passed to
70
+ # the block and the result will be stored in the new Catalog.
71
+ def self.from_array(*args)
72
+ collected = []
73
+ data = args.flatten
74
+
75
+ max = ((data.size % 2 == 0) ? data.size-1 : data.size-2)
76
+ (0..max).step(2) do |index|
77
+ key = block_given? ? yield(data[index]) : data[index]
78
+ value = block_given? ? yield(data[index+1]) : data[index+1]
79
+ collected << [key, value]
80
+ end
81
+
82
+ catalog = Catalog.new
83
+ catalog.instance_variable_set(:@data, collected)
84
+ return catalog
85
+ end
86
+
87
+ # Creates a new Catalog object from an array of key/value pairs.
88
+ # Each key/value pair in the source array will be stored in the new
89
+ # Catalog. If a block is given each value in the from the source array
90
+ # will be passed to the block and the result will be stored as the
91
+ # value in the Catalog.
92
+ def self.from_catalog(data, *args)
93
+ collected = []
94
+
95
+ if args.empty? && data.size == 2 && !data.first.is_a?(Array)
96
+ # Catalog.from_catalog([:one, 1])
97
+ data = [data]
98
+ elsif !args.empty?
99
+ #Catalog.from_catalog([:one, 1], [:two, 2], [:three, 3])
100
+ data = [data] + args
101
+ end
102
+
103
+ data.each do |item|
104
+ if block_given?
105
+ collected << [item.first, yield(item.last)]
106
+ else
107
+ collected << item
108
+ end
109
+ end
110
+
111
+ catalog = Catalog.new
112
+ catalog.instance_variable_set(:@data, collected)
113
+ return catalog
114
+ end
115
+
116
+ class << self
117
+ alias :from_catalogue :from_catalog
118
+ end
119
+
120
+ # Returns true if self array contains no elements.
121
+ def empty?
122
+ size == 0
123
+ end
124
+
125
+ # Returns the number of elements in self. May be zero.
126
+ def length
127
+ @data.length
128
+ end
129
+
130
+ alias :size :length
131
+
132
+ # Returns the first element, or the first n elements, of the array.
133
+ # If the array is empty, the first form returns nil, and the second
134
+ # form returns an empty array.
135
+ def first
136
+ @data.first
137
+ end
138
+
139
+ # Returns the last element(s) of self. If the array is empty,
140
+ # the first form returns nil.
141
+ def last
142
+ @data.last
143
+ end
144
+
145
+ # Equality—Two arrays are equal if they contain the same number of
146
+ # elements and if each element is equal to (according to Object.==)
147
+ # the corresponding element in the other array.
148
+ def ==(other)
149
+ if other.is_a? Catalog
150
+ return (@data == other.instance_variable_get(:@data))
151
+ elsif other.is_a? Array
152
+ return (@data == other)
153
+ else
154
+ return false
155
+ end
156
+ end
157
+
158
+ alias :eql? :==
159
+
160
+ # Comparison—Returns an integer (-1, 0, or +1) if this array is less
161
+ # than, equal to, or greater than other_ary. Each object in each
162
+ # array is compared (using <=>). If any value isn’t equal, then that
163
+ # inequality is the return value. If all the values found are equal,
164
+ # then the return is based on a comparison of the array lengths. Thus,
165
+ # two arrays are “equal” according to Array#<=> if and only if they have
166
+ # the same length and the value of each element is equal to the value of
167
+ # the corresponding element in the other array.
168
+ def <=>(other)
169
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
170
+ if other.is_a? Array
171
+ return @data <=> other
172
+ else
173
+ raise TypeError.new("can't convert #{other.class} into Catalog")
174
+ end
175
+ end
176
+
177
+ alias :compare :<=>
178
+ alias :compare_to :<=>
179
+
180
+ # Returns a new array populated with the given objects.
181
+ def [](index)
182
+ datum = @data[index]
183
+ return (datum.nil? ? nil : datum.dup)
184
+ end
185
+
186
+ alias :at :[]
187
+
188
+ # Element Assignment—Sets the element at index, or replaces a subarray starting
189
+ # at start and continuing for length elements, or replaces a subarray specified
190
+ # by range. If indices are greater than the current capacity of the array, the
191
+ # array grows automatically. A negative indices will count backward from the end
192
+ # of the array. Inserts elements if length is zero. An IndexError is raised if a
193
+ # negative index points past the beginning of the array. See also Array#push,
194
+ # and Array#unshift.
195
+ def []=(index, value)
196
+ if (index >= 0 && index >= @data.size) || (index < 0 && index.abs > @data.size)
197
+ raise ArgumentError.new('index must reference an existing element')
198
+ elsif value.is_a?(Hash) && value.size == 1
199
+ @data[index] = [value.keys.first, value.values.first]
200
+ elsif value.is_a?(Array) && value.size == 2
201
+ @data[index] = value.dup
202
+ else
203
+ raise ArgumentError.new('value must be a one-element hash or a two-element array')
204
+ end
205
+ end
206
+
207
+ # Returns a string representation of Catalog.
208
+ def to_s
209
+ return @data.to_s
210
+ end
211
+
212
+ # Set Intersection—Returns a new array containing elements common to the two
213
+ # arrays, with no duplicates.
214
+ def &(other)
215
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
216
+ if other.is_a? Array
217
+ return Catalog.from_catalog(@data & other)
218
+ else
219
+ raise TypeError.new("can't convert #{other.class} into Catalog")
220
+ end
221
+ end
222
+
223
+ alias :intersection :&
224
+
225
+ # Concatenation—Returns a new array built by concatenating the two arrays
226
+ # together to produce a third array.
227
+ def +(other)
228
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
229
+ if other.is_a? Array
230
+ return Catalog.from_catalog(@data + other)
231
+ else
232
+ raise TypeError.new("can't convert #{other.class} into Catalog")
233
+ end
234
+ end
235
+
236
+ alias :add :+
237
+ alias :sum :+
238
+
239
+ # Set Union—Returns a new array by joining this array with other_array,
240
+ # removing duplicates.
241
+ def |(other)
242
+ other = other.instance_variable_get(:@data) if other.is_a?(Catalog)
243
+ if other.is_a? Array
244
+ return Catalog.from_catalog(@data | other)
245
+ else
246
+ raise TypeError.new("can't convert #{other.class} into Catalog")
247
+ end
248
+ end
249
+
250
+ alias :union :|
251
+
252
+ # Append—Pushes the given object(s) on to the end of this array.
253
+ # This expression returns the array itself, so several appends
254
+ # may be chained together.
255
+ def push(item)
256
+ if item.is_a?(Hash) && item.size == 1
257
+ @data << [item.keys.first, item.values.first]
258
+ return self
259
+ elsif item.is_a?(Array) && item.size == 2
260
+ @data << item
261
+ return self
262
+ else
263
+ raise TypeError.new("can't convert #{item.class} into Catalog")
264
+ end
265
+ end
266
+
267
+ alias :<< :push
268
+ alias :append :push
269
+
270
+ # Removes the last element from self and returns it, or nil if the
271
+ # Catalog is empty.
272
+ def pop
273
+ if self.empty?
274
+ return nil
275
+ else
276
+ return @data.pop
277
+ end
278
+ end
279
+
280
+ # Copies the last element from self and returns it, or nil if the
281
+ # Catalog is empty.
282
+ def peek
283
+ if self.empty?
284
+ return nil
285
+ else
286
+ return @data.last.dup
287
+ end
288
+ end
289
+
290
+ # Returns a new array populated with the keys from this hash.
291
+ # See also Hash#values.
292
+ def keys
293
+ return @data.collect{|item| item.first}
294
+ end
295
+
296
+ # Returns a new array populated with the values from hsh.
297
+ # See also Hash#keys.
298
+ def values
299
+ return @data.collect{|item| item.last}
300
+ end
301
+
302
+ # Calls block once for each key in hsh, passing the key and value
303
+ # to the block as a two-element array. Because of the assignment
304
+ # semantics of block parameters, these elements will be split out
305
+ # if the block has two formal parameters. Also see Hash.each_pair,
306
+ # which will be marginally more efficient for blocks with two
307
+ # parameters.
308
+ def each(&block)
309
+ @data.each do |item|
310
+ yield(item)
311
+ end
312
+ end
313
+
314
+ # Calls block once for each key in hsh, passing the key and value as parameters.
315
+ def each_pair(&block)
316
+ @data.each do |item|
317
+ yield(item.first, item.last)
318
+ end
319
+ end
320
+
321
+ # Calls block once for each key in hsh, passing the key as a parameter.
322
+ def each_key(&block)
323
+ @data.each do |item|
324
+ yield(item.first)
325
+ end
326
+ end
327
+
328
+ # Calls block once for each key in hsh, passing the value as a parameter.
329
+ def each_value(&block)
330
+ @data.each do |item|
331
+ yield(item.last)
332
+ end
333
+ end
334
+
335
+ # Returns true if the given object is present in self (that is,
336
+ # if any object == anObject), false otherwise.
337
+ def include?(key=nil, value=nil)
338
+ if key && value
339
+ return @data.include?([key, value])
340
+ elsif key.is_a?(Array)
341
+ return @data.include?(key)
342
+ elsif key.is_a?(Hash) && key.size == 1
343
+ return @data.include?([key.keys.first, key.values.first])
344
+ else
345
+ return false
346
+ end
347
+ end
348
+
349
+ # Element Reference—Returns the element at index, or returns a
350
+ # subarray starting at start and continuing for length elements,
351
+ # or returns a subarray specified by range. Negative indices count
352
+ # backward from the end of the array (-1 is the last element).
353
+ # Returns nil if the index (or starting index) are out of range.
354
+ def slice(index, length=nil)
355
+ if length.nil?
356
+ catalog = @data.slice(index)
357
+ else
358
+ catalog = @data.slice(index, length)
359
+ end
360
+ return Catalog.new(catalog)
361
+ end
362
+
363
+ # Deletes the element(s) given by an index (optionally with a length)
364
+ # or by a range. Returns the deleted object, subarray, or nil if the
365
+ # index is out of range.
366
+ def slice!(index, length=nil)
367
+ if length.nil?
368
+ catalog = @data.slice!(index)
369
+ else
370
+ catalog = @data.slice!(index, length)
371
+ end
372
+ return Catalog.new(catalog)
373
+ end
374
+
375
+ # Return a new Catalog created by sorting self according to the natural
376
+ # sort order of the keys.
377
+ def sort_by_key
378
+ sorted = @data.sort{|a, b| a.first <=> b.first}
379
+ return Catalog.new(sorted)
380
+ end
381
+
382
+ # Sort self according to the natural sort order of the keys. Returns self.
383
+ def sort_by_key!
384
+ sorted = @data.sort!{|a, b| a.first <=> b.first}
385
+ return self
386
+ end
387
+
388
+ # Return a new Catalog created by sorting self according to the natural
389
+ # sort order of the values.
390
+ def sort_by_value
391
+ sorted = @data.sort{|a, b| a.last <=> b.last}
392
+ return Catalog.new(sorted)
393
+ end
394
+
395
+ # Sort self according to the natural sort order of the values. Returns self.
396
+ def sort_by_value!
397
+ sorted = @data.sort!{|a, b| a.last <=> b.last}
398
+ return self
399
+ end
400
+
401
+ # Returns a new array created by sorting self. Comparisons for
402
+ # the sort will be done using the <=> operator or using an
403
+ # optional code block. The block implements a comparison between
404
+ # a and b, returning -1, 0, or +1. See also Enumerable#sort_by.
405
+ def sort(&block)
406
+ sorted = @data.sort(&block)
407
+ return Catalog.new(sorted)
408
+ end
409
+
410
+ # Sorts self. Comparisons for the sort will be done using the <=>
411
+ # operator or using an optional code block. The block implements a
412
+ # comparison between a and b, returning -1, 0, or +1.
413
+ # See also Enumerable#sort_by.
414
+ def sort!(&block)
415
+ sorted = @data.sort!(&block)
416
+ return self
417
+ end
418
+
419
+ # Returns a new array that is a one-dimensional flattening of self.
420
+ def to_a
421
+ return @data.flatten
422
+ end
423
+
424
+ # Returns a new hash by converting each key/value pair in self into
425
+ # a key/value pair in the hash. When duplicate keys are encountered
426
+ # the last value associated with that key is kept and the others are
427
+ # discarded.
428
+ def to_hash
429
+ catalog = {}
430
+ @data.each do |item|
431
+ catalog[item.first] = item.last
432
+ end
433
+ return catalog
434
+ end
435
+
436
+ # Returns a new array that is the dat equivalent of self where each
437
+ # key/value pair is an two-element array within the returned array.
438
+ def to_catalog
439
+ return @data.dup
440
+ end
441
+
442
+ alias :to_catalogue :to_catalog
443
+
444
+ # Deletes items from self that are equal to obj. If the item is
445
+ # not found, returns nil. If the optional code block is given,
446
+ # returns the result of block if the item is not found.
447
+ def delete(key, value=nil)
448
+ item = nil
449
+
450
+ if key && value
451
+ item = @data.delete([key, value])
452
+ elsif key.is_a? Array
453
+ item = @data.delete(key)
454
+ elsif key.is_a? Hash
455
+ item = @data.delete([key.keys.first, key.values.first])
456
+ end
457
+
458
+ item = yield if item.nil? && block_given?
459
+ return item
460
+ end
461
+
462
+ # Deletes the element at the specified index, returning that element,
463
+ # or nil if the index is out of range. See also Array#slice!.
464
+ def delete_at(index)
465
+ item = @data.delete_at(index)
466
+ item = yield if item.nil? && block_given?
467
+ return item
468
+ end
469
+
470
+ # Deletes every element of self for which block evaluates to true.
471
+ def delete_if(&block)
472
+ raise ArgumentError.new('no block supplied') unless block_given?
473
+ if block.arity <= 1
474
+ items = @data.delete_if(&block)
475
+ else
476
+ items = []
477
+ @data.each do |key, value|
478
+ items << [key, value] if yield(key, value)
479
+ end
480
+ items.each {|item| @data.delete(item)}
481
+ end
482
+ return self
483
+ end
484
+ end
485
+
486
+ class Catalogue < Catalog; end
487
+ end