functional-ruby 0.5.0 → 0.6.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -562
  3. data/lib/functional/agent.rb +130 -0
  4. data/lib/functional/all.rb +9 -1
  5. data/lib/functional/behavior.rb +72 -39
  6. data/lib/functional/cached_thread_pool.rb +122 -0
  7. data/lib/functional/concurrency.rb +32 -24
  8. data/lib/functional/core.rb +2 -62
  9. data/lib/functional/event.rb +53 -0
  10. data/lib/functional/event_machine_defer_proxy.rb +23 -0
  11. data/lib/functional/fixed_thread_pool.rb +89 -0
  12. data/lib/functional/future.rb +42 -0
  13. data/lib/functional/global_thread_pool.rb +3 -0
  14. data/lib/functional/obligation.rb +121 -0
  15. data/lib/functional/promise.rb +194 -0
  16. data/lib/functional/thread_pool.rb +61 -0
  17. data/lib/functional/utilities.rb +114 -0
  18. data/lib/functional/version.rb +1 -1
  19. data/lib/functional.rb +1 -0
  20. data/lib/functional_ruby.rb +1 -0
  21. data/md/behavior.md +147 -0
  22. data/md/concurrency.md +465 -0
  23. data/md/future.md +32 -0
  24. data/md/obligation.md +32 -0
  25. data/md/pattern_matching.md +512 -0
  26. data/md/promise.md +220 -0
  27. data/md/utilities.md +53 -0
  28. data/spec/functional/agent_spec.rb +405 -0
  29. data/spec/functional/behavior_spec.rb +12 -33
  30. data/spec/functional/cached_thread_pool_spec.rb +112 -0
  31. data/spec/functional/concurrency_spec.rb +55 -0
  32. data/spec/functional/event_machine_defer_proxy_spec.rb +246 -0
  33. data/spec/functional/event_spec.rb +114 -0
  34. data/spec/functional/fixed_thread_pool_spec.rb +84 -0
  35. data/spec/functional/future_spec.rb +115 -0
  36. data/spec/functional/obligation_shared.rb +121 -0
  37. data/spec/functional/pattern_matching_spec.rb +10 -8
  38. data/spec/functional/promise_spec.rb +310 -0
  39. data/spec/functional/thread_pool_shared.rb +209 -0
  40. data/spec/functional/utilities_spec.rb +149 -0
  41. data/spec/spec_helper.rb +2 -0
  42. metadata +55 -5
@@ -0,0 +1,42 @@
1
+ require 'thread'
2
+
3
+ require 'functional/obligation'
4
+ require 'functional/global_thread_pool'
5
+
6
+ module Functional
7
+
8
+ class Future
9
+ include Obligation
10
+ behavior(:future)
11
+
12
+ def initialize(*args)
13
+
14
+ unless block_given?
15
+ @state = :fulfilled
16
+ else
17
+ @value = nil
18
+ @state = :pending
19
+ $GLOBAL_THREAD_POOL.post do
20
+ semaphore.synchronize do
21
+ Thread.pass
22
+ begin
23
+ @value = yield(*args)
24
+ @state = :fulfilled
25
+ rescue Exception => ex
26
+ @state = :rejected
27
+ @reason = ex
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module Kernel
37
+
38
+ def future(*args, &block)
39
+ return Functional::Future.new(*args, &block)
40
+ end
41
+ module_function :future
42
+ end
@@ -0,0 +1,3 @@
1
+ require 'functional/cached_thread_pool'
2
+
3
+ $GLOBAL_THREAD_POOL ||= Functional::CachedThreadPool.new
@@ -0,0 +1,121 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ require 'functional/behavior'
5
+
6
+ behavior_info(:future,
7
+ state: 0,
8
+ value: -1,
9
+ reason: 0,
10
+ pending?: 0,
11
+ fulfilled?: 0,
12
+ rejected?: 0)
13
+
14
+ behavior_info(:promise,
15
+ state: 0,
16
+ value: -1,
17
+ reason: 0,
18
+ pending?: 0,
19
+ fulfilled?: 0,
20
+ rejected?: 0,
21
+ then: 0,
22
+ rescue: -1)
23
+
24
+ module Functional
25
+
26
+ module Obligation
27
+
28
+ attr_reader :state
29
+ attr_reader :reason
30
+
31
+ # Has the obligation been fulfilled?
32
+ # @return [Boolean]
33
+ def fulfilled?() return(@state == :fulfilled); end
34
+ alias_method :realized?, :fulfilled?
35
+
36
+ # Is obligation completion still pending?
37
+ # @return [Boolean]
38
+ def pending?() return(!(fulfilled? || rejected?)); end
39
+
40
+ def value(timeout = nil)
41
+ if !pending? || timeout == 0
42
+ return @value
43
+ elsif timeout.nil?
44
+ return semaphore.synchronize { @value }
45
+ else
46
+ begin
47
+ return Timeout::timeout(timeout.to_f) {
48
+ semaphore.synchronize { @value }
49
+ }
50
+ rescue Timeout::Error => ex
51
+ return nil
52
+ end
53
+ end
54
+ end
55
+ alias_method :deref, :value
56
+
57
+ # Has the promise been rejected?
58
+ # @return [Boolean]
59
+ def rejected?() return(@state == :rejected); end
60
+
61
+ protected
62
+
63
+ def semaphore
64
+ @semaphore ||= Mutex.new
65
+ end
66
+ end
67
+ end
68
+
69
+ module Kernel
70
+
71
+ def deref(obligation, timeout = nil)
72
+ if obligation.respond_to?(:deref)
73
+ return obligation.deref(timeout)
74
+ elsif obligation.respond_to?(:value)
75
+ return obligation.deref(timeout)
76
+ else
77
+ return nil
78
+ end
79
+ end
80
+ module_function :deref
81
+
82
+ def pending?(obligation)
83
+ if obligation.respond_to?(:pending?)
84
+ return obligation.pending?
85
+ else
86
+ return false
87
+ end
88
+ end
89
+ module_function :pending?
90
+
91
+ def fulfilled?(obligation)
92
+ if obligation.respond_to?(:fulfilled?)
93
+ return obligation.fulfilled?
94
+ elsif obligation.respond_to?(:realized?)
95
+ return obligation.realized?
96
+ else
97
+ return false
98
+ end
99
+ end
100
+ module_function :fulfilled?
101
+
102
+ def realized?(obligation)
103
+ if obligation.respond_to?(:realized?)
104
+ return obligation.realized?
105
+ elsif obligation.respond_to?(:fulfilled?)
106
+ return obligation.fulfilled?
107
+ else
108
+ return false
109
+ end
110
+ end
111
+ module_function :realized?
112
+
113
+ def rejected?(obligation)
114
+ if obligation.respond_to?(:rejected?)
115
+ return obligation.rejected?
116
+ else
117
+ return false
118
+ end
119
+ end
120
+ module_function :rejected?
121
+ end
@@ -0,0 +1,194 @@
1
+ require 'thread'
2
+
3
+ require 'functional/obligation'
4
+ require 'functional/global_thread_pool'
5
+
6
+ module Functional
7
+
8
+ class Promise
9
+ include Obligation
10
+ behavior(:future)
11
+ behavior(:promise)
12
+
13
+ # Creates a new promise object. "A promise represents the eventual
14
+ # value returned from the single completion of an operation."
15
+ # Promises can be chained in a tree structure where each promise
16
+ # has zero or more children. Promises are resolved asynchronously
17
+ # in the order they are added to the tree. Parents are guaranteed
18
+ # to be resolved before their children. The result of each promise
19
+ # is passed to each of its children upon resolution. When
20
+ # a promise is rejected all its children will be summarily rejected.
21
+ # A promise that is neither resolved or rejected is pending.
22
+ #
23
+ # @param args [Array] zero or more arguments for the block
24
+ # @param block [Proc] the block to call when attempting fulfillment
25
+ #
26
+ # @see http://wiki.commonjs.org/wiki/Promises/A
27
+ # @see http://promises-aplus.github.io/promises-spec/
28
+ def initialize(*args, &block)
29
+ if args.first.is_a?(Promise)
30
+ @parent = args.first
31
+ else
32
+ @parent = nil
33
+ @chain = [self]
34
+ end
35
+
36
+ @mutex = Mutex.new
37
+ @handler = block || Proc.new{|result| result }
38
+ @state = :pending
39
+ @value = nil
40
+ @reason = nil
41
+ @children = []
42
+ @rescuers = []
43
+
44
+ realize(*args) if root?
45
+ end
46
+
47
+ # Create a new child Promise. The block argument for the child will
48
+ # be the result of fulfilling its parent. If the child will
49
+ # immediately be rejected if the parent has already been rejected.
50
+ #
51
+ # @param block [Proc] the block to call when attempting fulfillment
52
+ #
53
+ # @return [Promise] the new promise
54
+ def then(&block)
55
+ child = @mutex.synchronize do
56
+ block = Proc.new{|result| result } unless block_given?
57
+ @children << Promise.new(self, &block)
58
+ @children.last.on_reject(@reason) if rejected?
59
+ push(@children.last)
60
+ @children.last
61
+ end
62
+ return child
63
+ end
64
+
65
+ # Add a rescue handler to be run if the promise is rejected (via raised
66
+ # exception). Multiple rescue handlers may be added to a Promise.
67
+ # Rescue blocks will be checked in order and the first one with a
68
+ # matching Exception class will be processed. The block argument
69
+ # will be the exception that caused the rejection.
70
+ #
71
+ # @param clazz [Class] The class of exception to rescue
72
+ # @param block [Proc] the block to call if the rescue is matched
73
+ #
74
+ # @return [self] so that additional chaining can occur
75
+ def rescue(clazz = Exception, &block)
76
+ @mutex.synchronize do
77
+ @rescuers << Rescuer.new(clazz, block) if block_given?
78
+ end
79
+ return self
80
+ end
81
+ alias_method :catch, :rescue
82
+ alias_method :on_error, :rescue
83
+
84
+ protected
85
+
86
+ attr_reader :parent
87
+ attr_reader :handler
88
+ attr_reader :rescuers
89
+
90
+ # @private
91
+ Rescuer = Struct.new(:clazz, :block)
92
+
93
+ # @private
94
+ def root # :nodoc:
95
+ current = self
96
+ current = current.parent until current.root?
97
+ return current
98
+ end
99
+
100
+ # @private
101
+ def root? # :nodoc:
102
+ @parent.nil?
103
+ end
104
+
105
+ # @private
106
+ def push(promise) # :nodoc:
107
+ if root?
108
+ @chain << promise
109
+ else
110
+ @parent.push(promise)
111
+ end
112
+ end
113
+
114
+ # @private
115
+ def on_fulfill(value) # :nodoc:
116
+ @mutex.synchronize do
117
+ if pending?
118
+ @value = @handler.call(value)
119
+ @state = :fulfilled
120
+ @reason = nil
121
+ end
122
+ end
123
+ return @value
124
+ end
125
+
126
+ # @private
127
+ def on_reject(reason) # :nodoc:
128
+ @mutex.synchronize do
129
+ if pending?
130
+ @state = :rejected
131
+ @reason = reason
132
+ self.try_rescue(reason)
133
+ @value = nil
134
+ end
135
+ @children.each{|child| child.on_reject(reason) }
136
+ end
137
+ end
138
+
139
+ # @private
140
+ def try_rescue(ex) # :nodoc:
141
+ rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
142
+ rescuer.block.call(ex) if rescuer
143
+ rescue Exception => e
144
+ # supress
145
+ end
146
+
147
+ # @private
148
+ def realize(*args) # :nodoc:
149
+ $GLOBAL_THREAD_POOL.post(@chain, @mutex, args) do |chain, mutex, args|
150
+ result = args.length == 1 ? args.first : args
151
+ index = 0
152
+ loop do
153
+ Thread.pass
154
+ current = mutex.synchronize{ chain[index] }
155
+ unless current.rejected?
156
+ current.semaphore.synchronize do
157
+ begin
158
+ result = current.on_fulfill(result)
159
+ rescue Exception => ex
160
+ current.on_reject(ex)
161
+ end
162
+ end
163
+ end
164
+ index += 1
165
+ sleep while index >= chain.length
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ module Kernel
173
+
174
+ # Creates a new promise object. "A promise represents the eventual
175
+ # value returned from the single completion of an operation."
176
+ # Promises can be chained in a tree structure where each promise
177
+ # has zero or more children. Promises are resolved asynchronously
178
+ # in the order they are added to the tree. Parents are guaranteed
179
+ # to be resolved before their children. The result of each promise
180
+ # is passes to each of its children when the child resolves. When
181
+ # a promise is rejected all its children will be summarily rejected.
182
+ # A promise added to a rejected promise will immediately be rejected.
183
+ # A promise that is neither resolved or rejected is pending.
184
+ #
185
+ # @param args [Array] zero or more arguments for the block
186
+ # @param block [Proc] the block to call when attempting fulfillment
187
+ #
188
+ # @see Promise
189
+ # @see http://wiki.commonjs.org/wiki/Promises/A
190
+ def promise(*args, &block)
191
+ return Functional::Promise.new(*args, &block)
192
+ end
193
+ module_function :promise
194
+ end
@@ -0,0 +1,61 @@
1
+ require 'functional/behavior'
2
+ require 'functional/event'
3
+
4
+ behavior_info(:thread_pool,
5
+ running?: 0,
6
+ shutdown?: 0,
7
+ killed?: 0,
8
+ shutdown: 0,
9
+ kill: 0,
10
+ size: 0,
11
+ wait_for_termination: -1,
12
+ post: -1,
13
+ :<< => 1,
14
+ status: 0)
15
+
16
+ behavior_info(:global_thread_pool,
17
+ post: -1,
18
+ :<< => 1)
19
+
20
+ module Functional
21
+
22
+ class ThreadPool
23
+
24
+ def initialize
25
+ @status = :running
26
+ @queue = Queue.new
27
+ @termination = Event.new
28
+ @pool = []
29
+ end
30
+
31
+ def running?
32
+ return @status == :running
33
+ end
34
+
35
+ def shutdown?
36
+ return ! running?
37
+ end
38
+
39
+ def killed?
40
+ return @status == :killed
41
+ end
42
+
43
+ def shutdown
44
+ @pool.size.times{ @queue << :stop }
45
+ @status = :shuttingdown
46
+ end
47
+
48
+ def wait_for_termination(timeout = nil)
49
+ if shutdown? || killed?
50
+ return true
51
+ else
52
+ return @termination.wait(timeout)
53
+ end
54
+ end
55
+
56
+ def <<(block)
57
+ self.post(&block)
58
+ return self
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,114 @@
1
+ require 'pp'
2
+ require 'stringio'
3
+ require 'erb'
4
+
5
+ Infinity = 1/0.0 unless defined?(Infinity)
6
+ NaN = 0/0.0 unless defined?(NaN)
7
+
8
+ module Kernel
9
+
10
+ # Compute the difference (delta) between two values.
11
+ #
12
+ # When a block is given the block will be applied to both arguments.
13
+ # Using a block in this way allows computation against a specific field
14
+ # in a data set of hashes or objects.
15
+ #
16
+ # @yield iterates over each element in the data set
17
+ # @yieldparam item each element in the data set
18
+ #
19
+ # @param [Object] v1 the first value
20
+ # @param [Object] v2 the second value
21
+ #
22
+ # @return [Float] positive value representing the difference
23
+ # between the two parameters
24
+ def delta(v1, v2)
25
+ if block_given?
26
+ v1 = yield(v1)
27
+ v2 = yield(v2)
28
+ end
29
+ return (v1 - v2).abs
30
+ end
31
+ module_function :delta
32
+
33
+ # Sandbox the given operation at a high $SAFE level.
34
+ #
35
+ # @param args [Array] zero or more arguments to pass to the block
36
+ # @param block [Proc] the block to isolate
37
+ #
38
+ # @return [Object] the result of the block operation
39
+ def safe(*args)
40
+ raise ArgumentError.new('no block given') unless block_given?
41
+ result = nil
42
+ t = Thread.new do
43
+ $SAFE = 3
44
+ result = yield(*args)
45
+ end
46
+ t.join
47
+ return result
48
+ end
49
+ module_function :safe
50
+
51
+ # Open a file, read it, close the file, and return its contents.
52
+ #
53
+ # @param file [String] path to and name of the file to open
54
+ # @return [String] file contents
55
+ #
56
+ # @see slurpee
57
+ def slurp(file)
58
+ File.open(file, 'rb') {|f| f.read }
59
+ end
60
+ module_function :slurp
61
+
62
+ # Open a file, read it, close the file, run the contents through the
63
+ # ERB parser, and return updated contents.
64
+ #
65
+ # @param file [String] path to and name of the file to open
66
+ # @param safe_level [Integer] when not nil, ERB will $SAFE set to this
67
+ # @return [String] file contents
68
+ #
69
+ # @see slurpee
70
+ def slurpee(file, safe_level = nil)
71
+ ERB.new(slurp(file), safe_level).result
72
+ end
73
+ module_function :slurpee
74
+
75
+ #############################################################################
76
+
77
+ # @private
78
+ def repl? # :nodoc:
79
+ return ($0 == 'irb' || $0 == 'pry' || $0 == 'script/rails' || !!($0 =~ /bin\/bundle$/))
80
+ end
81
+ module_function :repl?
82
+
83
+ # @private
84
+ def timestamp # :nodoc:
85
+ return Time.now.getutc.to_i
86
+ end
87
+
88
+ # @private
89
+ def timer(*args) # :nodoc:
90
+ t1 = Time.now
91
+ result = yield(*args)
92
+ t2 = Time.now
93
+ return (t2 - t1)
94
+ end
95
+ module_function :timer
96
+
97
+ # @private
98
+ def strftimer(seconds) # :nodoc:
99
+ Time.at(seconds).gmtime.strftime('%R:%S.%L')
100
+ end
101
+ module_function :strftimer
102
+
103
+ # @private
104
+ # @see http://rhaseventh.blogspot.com/2008/07/ruby-and-rails-how-to-get-pp-pretty.html
105
+ def pp_s(*objs) # :nodoc:
106
+ s = StringIO.new
107
+ objs.each {|obj|
108
+ PP.pp(obj, s)
109
+ }
110
+ s.rewind
111
+ s.read
112
+ end
113
+ module_function :pp_s
114
+ end
@@ -1,3 +1,3 @@
1
1
  module Functional
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
data/lib/functional.rb ADDED
@@ -0,0 +1 @@
1
+ require 'functional/all'
@@ -0,0 +1 @@
1
+ require 'functional/all'
data/md/behavior.md ADDED
@@ -0,0 +1,147 @@
1
+ # For good -behavior(timeoff).
2
+
3
+ One of Ruby's greatest strengths is [duck typing](http://rubylearning.com/satishtalim/duck_typing.html).
4
+ Usually this is awesome and I'm happy to not have to deal with static typing and the compiler. Usually.
5
+ The problem with duck typing is that is is impossible in Ruby to enforce an interface definition.
6
+ I would never advocate turning Ruby into the cesspool complex object creation that Java has
7
+ unfortunately become, but occasionally it would be nice to make sure a class implements a set of
8
+ required methods. Enter Erlang's [-behavior](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/)
9
+ keyword. Basically, you define a `behavior_info` then drop a `behavior` call within a class.
10
+ Forget to implement a required method and Ruby will let you know. See the examples below for details.
11
+
12
+ ## Usage
13
+
14
+ The `behavior` functionality is not imported by default. It needs a separate `require` statement:
15
+
16
+ ```ruby
17
+ require 'functional/behavior'
18
+
19
+ # -or-
20
+
21
+ require 'functional/behaviour'
22
+ ```
23
+
24
+ ### behavior_info
25
+
26
+ Next, declare a behavior using the `behavior_info` function (this function should sit outside
27
+ of any module/class definition, but will probably work regardless). The first parameter to
28
+ `behavior_info` (or `behaviour_info`) is a symbol name for the behavior. The remaining parameter
29
+ is a hash of function names and their arity:
30
+
31
+ ```ruby
32
+ behaviour_info(:gen_foo, foo: 0, bar: 1, baz: 2)
33
+
34
+ # -or (for the Java/C# crowd)
35
+
36
+ interface(:gen_foo, foo: 0, bar: 1, baz: 2)
37
+
38
+ ```
39
+
40
+ Each function name can be listed only once and the arity must follow the rules of the
41
+ [Method#arity](http://ruby-doc.org/core-1.9.3/Method.html#method-i-arity) function.
42
+ Though not explicitly documented, block arguments do not count toward a method's arity.
43
+ methods defined using this gem's `defn` function will always have an arity of -1,
44
+ regardless of how many overloads are defined.
45
+
46
+ ### behavior
47
+
48
+ To enforce a behavior on a class simply call the `behavior` function within the class,
49
+ passing the name of the desired behavior:
50
+
51
+ ```ruby
52
+ class Foo
53
+ behavior(:gen_foo)
54
+ ...
55
+ end
56
+
57
+ # or use the idiomatic Erlang spelling
58
+ class Bar
59
+ behaviour(:gen_foo)
60
+ ...
61
+ end
62
+
63
+ # or use the idiomatic Rails syntax
64
+ class Baz
65
+ behaves_as :gen_foo
66
+ ...
67
+ end
68
+ ```
69
+
70
+ Make sure you the implement the required methods in your class. If you don't, Ruby will
71
+ raise an exception when you try to create an object from the class:
72
+
73
+ ```ruby
74
+ Baz.new #=> ArgumentError: undefined callback functions in Baz (behavior 'gen_foo')
75
+ ```
76
+
77
+ ### behaves_as?
78
+
79
+ As an added bonus, Ruby [Object](http://ruby-doc.org/core-1.9.3/Object.html) will be
80
+ monkey-patched with a `behaves_as?` predicate method.
81
+
82
+ ## Example
83
+
84
+ A complete example:
85
+
86
+ ```ruby
87
+ behaviour_info(:gen_foo, foo: 0, bar: 1, baz: 2, boom: -1, bam: :any)
88
+
89
+ class Foo
90
+ behavior(:gen_foo)
91
+
92
+ def foo
93
+ return 'foo/0'
94
+ end
95
+
96
+ def bar(one, &block)
97
+ return 'bar/1'
98
+ end
99
+
100
+ def baz(one, two)
101
+ return 'baz/2'
102
+ end
103
+
104
+ def boom(*args)
105
+ return 'boom/-1'
106
+ end
107
+
108
+ def bam
109
+ return 'bam!'
110
+ end
111
+ end
112
+
113
+ foo = Foo.new
114
+
115
+ foo.behaves_as? :gen_foo #=> true
116
+ foo.behaves_as?(:bogus) #=> false
117
+ 'foo'.behaves_as? :gen_foo #=> false
118
+ ```
119
+
120
+ ## Copyright
121
+
122
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
123
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
124
+
125
+ ## License
126
+
127
+ Released under the MIT license.
128
+
129
+ http://www.opensource.org/licenses/mit-license.php
130
+
131
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
132
+ > of this software and associated documentation files (the "Software"), to deal
133
+ > in the Software without restriction, including without limitation the rights
134
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
135
+ > copies of the Software, and to permit persons to whom the Software is
136
+ > furnished to do so, subject to the following conditions:
137
+ >
138
+ > The above copyright notice and this permission notice shall be included in
139
+ > all copies or substantial portions of the Software.
140
+ >
141
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
142
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
143
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
144
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
145
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
146
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
147
+ > THE SOFTWARE.