functional-ruby 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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.