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
data/md/concurrency.md ADDED
@@ -0,0 +1,465 @@
1
+ # Go, Clojure, and JavaScript-inspired Concurrency
2
+
3
+ The old-school "lock and synchronize" approach to concurrency is dead. The future of concurrency
4
+ is asynchronous. Send out a bunch of independent [actors](http://en.wikipedia.org/wiki/Actor_model)
5
+ to do your bidding and process the results when you are ready. Although the idea of the concurrent
6
+ actor originated in the early 1970's it has only recently started catching on. Although there is
7
+ no one "true" actor implementation (what *exactly* is "object oriented," what *exactly* is
8
+ "functional programming"), many modern programming languages implement variations on the actor
9
+ theme. This library implements a few of the most interesting and useful of those variations.
10
+
11
+ Remember, *there is not silver bullet in concurrent programming.* Concurrency is hard. Very hard.
12
+ These tools will help ease the burden, but at the end of the day it is essential that you
13
+ *know what you are doing.*
14
+
15
+ ## Agent
16
+
17
+ Agents are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) keyword.
18
+ An agent is a single atomic value that represents an identity. The current value
19
+ of the agent can be requested at any time (`deref`). Each agent has a work queue and operates on
20
+ the global thread pool (see below). Consumers can `post` code blocks to the
21
+ agent. The code block (function) will receive the current value of the agent as its sole
22
+ parameter. The return value of the block will become the new value of the agent. Agents support
23
+ two error handling modes: fail and continue. A good example of an agent is a shared incrementing
24
+ counter, such as the score in a video game.
25
+
26
+ An agent must be initialize with an initial value. This value is always accessible via the `value`
27
+ (or `deref`) methods. Code blocks sent to the agent will be processed in the order received. As
28
+ each block is processed the current value is updated with the result from the block. This update
29
+ is an atomic operation so a `deref` will never block and will always return the current value.
30
+
31
+ When an agent is created it may be given an optional `validate` block and zero or more `rescue`
32
+ blocks. When a new value is calculated the value will be checked against the validator, if present.
33
+ If the validator returns `true` the new value will be accepted. If it returns `false` it will be
34
+ rejected. If a block raises an exception during execution the list of `rescue` blocks will be
35
+ seacrhed in order until one matching the current exception is found. That `rescue` block will
36
+ then be called an passed the exception object. If no matching `rescue` block is found, or none
37
+ were configured, then the exception will be suppressed.
38
+
39
+ ### Examples
40
+
41
+ A simple example:
42
+
43
+ ```ruby
44
+ require 'functional/agent'
45
+ # or
46
+ require 'functional/concurrency'
47
+
48
+ score = Functional::Agent.new(10)
49
+ score.value #=> 10
50
+
51
+ score << proc{|current| current + 100 }
52
+ sleep(0.1)
53
+ score.value #=> 110
54
+
55
+ score << proc{|current| current * 2 }
56
+ sleep(0.1)
57
+ deref score #=> 220
58
+
59
+ score << proc{|current| current - 50 }
60
+ sleep(0.1)
61
+ score.value #=> 170
62
+ ```
63
+
64
+ With validation and error handling:
65
+
66
+ ```ruby
67
+ score = agent(0).validate{|value| value <= 1024 }.
68
+ rescue(NoMethodError){|ex| puts "Bam!" }.
69
+ rescue(ArgumentError){|ex| puts "Pow!" }.
70
+ rescue{|ex| puts "Boom!" }
71
+ score.value #=> 0
72
+
73
+ score << proc{|current| current + 2048 }
74
+ sleep(0.1)
75
+ score.value #=> 0
76
+
77
+ score << proc{|current| raise ArgumentError }
78
+ sleep(0.1)
79
+ #=> puts "Pow!"
80
+ score.value #=> 0
81
+
82
+ score << proc{|current| current + 100 }
83
+ sleep(0.1)
84
+ score.value #=> 100
85
+ ```
86
+
87
+ ## Future
88
+
89
+ Futures are inspired by [Clojure's](http://clojure.org/) [future](http://clojuredocs.org/clojure_core/clojure.core/future) keyword.
90
+ A future represents a promise to complete an action at some time in the future. The action is atomic and permanent.
91
+ The idea behind a future is to send an action off for asynchronous operation, do other stuff, then return and
92
+ retrieve the result of the async operation at a later time. Futures run on the global thread pool (see below).
93
+
94
+ Futures have three possible states: *pending*, *rejected*, and *fulfilled*. When a future is created it is set
95
+ to *pending* and will remain in that state until processing is complete. A completed future is either *rejected*,
96
+ indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a future is
97
+ *fulfilled* its `value` will be updated to reflect the result of the operation. If *rejected* the `reason` will
98
+ be updated with a reference to the thrown exception. The predicate methods `pending?`, `rejected`, and `fulfilled?`
99
+ can be called at any time to obtain the state of the future, as can the `state` method, which returns a symbol.
100
+
101
+ Retrieving the value of a future is done through the `value` (alias: `deref`) method. Obtaining the value of
102
+ a future is a potentially blocking operation. When a future is *rejected* a call to `value` will return `nil`
103
+ immediately. When a future is *fulfilled* a call to `value` will immediately return the current value.
104
+ When a future is *pending* a call to `value` will block until the future is either *rejected* or *fulfilled*.
105
+ A *timeout* value can be passed to `value` to limit how long the call will block. If `nil` the call will
106
+ block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
107
+ maximum number of seconds to block.
108
+
109
+ ### Examples
110
+
111
+ A fulfilled example:
112
+
113
+ ```ruby
114
+ require 'functional/future'
115
+ # or
116
+ require 'functional/concurrency'
117
+
118
+ count = Functional::Future{ sleep(10); 10 }
119
+ count.state #=> :pending
120
+ count.pending? #=> true
121
+
122
+ # do stuff...
123
+
124
+ count.value(0) #=> nil (does not block)
125
+
126
+ count.value #=> 10 (after blocking)
127
+ count.state #=> :fulfilled
128
+ count.fulfilled? #=> true
129
+ deref count #=> 10
130
+ ```
131
+
132
+ A rejected example:
133
+
134
+ ```ruby
135
+ count = future{ sleep(10); raise StandardError.new("Boom!") }
136
+ count.state #=> :pending
137
+ pending?(count) #=> true
138
+
139
+ deref(count) #=> nil (after blocking)
140
+ rejected?(count) #=> true
141
+ count.reason #=> #<StandardError: Boom!>
142
+ ```
143
+
144
+ ## Promise
145
+
146
+ A promise is the most powerfule and versatile of the concurrency objects in this library.
147
+ Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
148
+ and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications.
149
+
150
+ > A promise represents the eventual value returned from the single completion of an operation.
151
+
152
+ Promises are similar to futures and share many of the same behaviours. Promises are far more robust,
153
+ however. Promises can be chained in a tree structure where each promise may have zero or more children.
154
+ Promises are chained using the `then` method. The result of a call to `then` is always another promise.
155
+ Promises are resolved asynchronously in the order they are added to the tree. Parents are guaranteed
156
+ to be resolved before their children. The result of each promise is passed to each of its children
157
+ upon resolution. When a promise is rejected all its children will be summarily rejected.
158
+
159
+ Promises have three possible states: *pending*, *rejected*, and *fulfilled*. When a promise is created it is set
160
+ to *pending* and will remain in that state until processing is complete. A completed promise is either *rejected*,
161
+ indicating that an exception was thrown during processing, or *fulfilled*, indicating succedd. If a promise is
162
+ *fulfilled* its `value` will be updated to reflect the result of the operation. If *rejected* the `reason` will
163
+ be updated with a reference to the thrown exception. The predicate methods `pending?`, `rejected`, and `fulfilled?`
164
+ can be called at any time to obtain the state of the promise, as can the `state` method, which returns a symbol.
165
+
166
+ Retrieving the value of a promise is done through the `value` (alias: `deref`) method. Obtaining the value of
167
+ a promise is a potentially blocking operation. When a promise is *rejected* a call to `value` will return `nil`
168
+ immediately. When a promise is *fulfilled* a call to `value` will immediately return the current value.
169
+ When a promise is *pending* a call to `value` will block until the promise is either *rejected* or *fulfilled*.
170
+ A *timeout* value can be passed to `value` to limit how long the call will block. If `nil` the call will
171
+ block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
172
+ maximum number of seconds to block.
173
+
174
+ ### Examples
175
+
176
+ A simple example:
177
+
178
+ ```ruby
179
+ require 'functional/promise'
180
+ # or
181
+ require 'functional/concurrency'
182
+
183
+ p = Functional::Promise.new{ sleep(1); "Hello world!" }
184
+ p.value(0) #=> nil (does not block)
185
+ p.value #=> "Hello world!" (after blocking)
186
+ p.state #=> :fulfilled
187
+ ```
188
+
189
+ An example with chaining:
190
+
191
+ ```ruby
192
+ p = promise("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
193
+ then{|result| sleep(1); result}.
194
+ then{|result| "Hello #{result}." }.
195
+ then{|result| "#{result} Would you like to play a game?"}
196
+
197
+ p.pending? #=> true
198
+ p.value(0) #=> nil (does not block)
199
+
200
+ p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
201
+ ```
202
+
203
+ An example with error handling:
204
+
205
+ ```ruby
206
+ @expected = nil
207
+ p = promise{ raise ArgumentError }.
208
+ rescue(LoadError){|ex| @expected = 1 }.
209
+ rescue(ArgumentError){|ex| @expected = 2 }.
210
+ rescue(Exception){|ex| @expected = 3 }
211
+
212
+ sleep(0.1)
213
+
214
+ @expected #=> 2
215
+ pending?(p) #=> false
216
+ fulfilled?(p) #=> false
217
+ rejected?(p) #=> true
218
+
219
+ deref(p) #=> nil
220
+ p.reason #=> #<ArgumentError: ArgumentError>
221
+ ```
222
+
223
+ A complex example with chaining and error handling:
224
+
225
+ ```ruby
226
+ p = promise("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
227
+ then{|result| sleep(0.5); result}.
228
+ rescue(ArgumentError){|ex| puts "Pow!" }.
229
+ then{|result| "Hello #{result}." }.
230
+ rescue(NoMethodError){|ex| puts "Bam!" }.
231
+ rescue(ArgumentError){|ex| puts "Zap!" }.
232
+ then{|result| raise StandardError.new("Boom!") }.
233
+ rescue{|ex| puts ex.message }.
234
+ then{|result| "#{result} Would you like to play a game?"}
235
+
236
+ sleep(1)
237
+
238
+ p.value #=> nil
239
+ p.state #=> :rejected
240
+ p.reason #=> #<StandardError: Boom!>
241
+ ```
242
+
243
+ ## Goroutine
244
+
245
+ A goroutine is the simplest of the concurrency utilities in this library. It is inspired by
246
+ [Go's](http://golang.org/) [goroutines](https://gobyexample.com/goroutines) and
247
+ [Erlang's](http://www.erlang.org/) [spawn](http://erlangexamples.com/tag/spawn/) keyword. The
248
+ `go` function is nothing more than a simple way to send a block to the global thread pool (see below)
249
+ for processing.
250
+
251
+ ### Examples
252
+
253
+ ```ruby
254
+ require 'functional/concurrency'
255
+
256
+ @expected = nil
257
+
258
+ go(1, 2, 3){|a, b, c| sleep(1); @expected = [c, b, a] }
259
+
260
+ sleep(0.1)
261
+ @expected #=> nil
262
+
263
+ sleep(2)
264
+ @expected #=> [3, 2, 1]
265
+ ```
266
+
267
+ ## Thread Pools
268
+
269
+ Thread pools are neither a new idea nor an implementation of the actor patter. Nevertheless, thread
270
+ pools are still an extremely relevant concurrency tool. Every time a thread is created then
271
+ subsequently destroyed there is overhead. Creating a pool of reusable worker threads then repeatedly'
272
+ dipping into the pool can have huge performace benefits for a long-running application like a service.
273
+ Ruby's blocks provide an excellent mechanism for passing a generic work request to a thread, making
274
+ Ruby an excellent candidate language for thread pools.
275
+
276
+ The inspiration for thread pools in this library is Java's `java.util.concurrent` implementation of
277
+ [thread pools](java.util.concurrent). The `java.util.concurrent` library is a well-designed, stable,
278
+ scalable, and battle-tested concurrency library. It provides three different implementations of thread
279
+ pools. One of those implementations is simply a special case of the first and doesn't offer much
280
+ advantage in Ruby, so only the first two (`FixedThreadPool` and `CachedThreadPool`) are implemented here.
281
+
282
+ Thread pools share common `behavior` defined by `:thread_pool`. The most imortant method is `post`
283
+ (aliased with the left-shift operator `<<`). The `post` method sends a block to the pool for future
284
+ processing.
285
+
286
+ A running thread pool can be shutdown in an orderly or disruptive manner. Once a thread pool has been
287
+ shutdown in cannot be started again. The `shutdown` method can be used to initiate an orderly shutdown
288
+ of the thread pool. All new `post` calls will reject the given block and immediately return `false`.
289
+ Threads in the pool will continue to process all in-progress work and will process all tasks still in
290
+ the queue. The `kill` method can be used to immediately shutdown the pool. All new `post` calls will
291
+ reject the given block and immediately return `false`. Ruby's `Thread.kill` will be called on all threads
292
+ in the pool, aborting all in-progress work. Tasks in the queue will be discarded.
293
+
294
+ A client thread can choose to block and wait for pool shutdown to complete. This is useful when shutting
295
+ down an application and ensuring the app doesn't exit before pool processing is complete. The method
296
+ `wait_for_termination` will block for a maximum of the given number of seconds then return `true` if
297
+ shutdown completed successfully or `false`. When the timeout value is `nil` the call will block
298
+ indefinitely. Calling `wait_for_termination` on a stopped thread pool will immediately return `true`.
299
+
300
+ Predicate methods are provided to describe the current state of the thread pool. Provided methods are
301
+ `running?`, `shutdown?`, and `killed?`. The `shutdown` method will return true regardless of whether
302
+ the pool was shutdown wil `shutdown` or `kill`.
303
+
304
+ ### FixedThreadPool
305
+
306
+ From the docs:
307
+
308
+ > Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
309
+ > At any point, at most `nThreads` threads will be active processing tasks. If additional tasks are submitted
310
+ > when all threads are active, they will wait in the queue until a thread is available. If any thread terminates
311
+ > due to a failure during execution prior to shutdown, a new one will take its place if needed to execute
312
+ > subsequent tasks. The threads in the pool will exist until it is explicitly `shutdown`.
313
+
314
+ #### Examples
315
+
316
+ ```ruby
317
+ require 'functional/fixed_thread_pool'
318
+ # or
319
+ require 'functional/concurrency'
320
+
321
+ pool = Functional::FixedThreadPool.new(5)
322
+
323
+ pool.size #=> 5
324
+ pool.running? #=> true
325
+ pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
326
+
327
+ pool.post(1,2,3){|*args| sleep(10) }
328
+ pool << proc{ sleep(10) }
329
+ pool.size #=> 5
330
+
331
+ sleep(11)
332
+ pool.status #=> ["sleep", "sleep", "sleep", "sleep", "sleep"]
333
+
334
+ pool.shutdown #=> :shuttingdown
335
+ pool.status #=> []
336
+ pool.wait_for_termination
337
+
338
+ pool.size #=> 0
339
+ pool.status #=> []
340
+ pool.shutdown? #=> true
341
+ ```
342
+
343
+ ### CachedThreadPool
344
+
345
+ From the docs:
346
+
347
+ > Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when
348
+ > they are available. These pools will typically improve the performance of programs that execute many short-lived
349
+ > asynchronous tasks. Calls to [`post`] will reuse previously constructed threads if available. If no existing
350
+ > thread is available, a new thread will be created and added to the pool. Threads that have not been used for
351
+ > sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will
352
+ > not consume any resources. Note that pools with similar properties but different details (for example,
353
+ > timeout parameters) may be created using [`CachedThreadPool`] constructors.
354
+
355
+ #### Examples
356
+
357
+ ```ruby
358
+ require 'functional/cached_thread_pool'
359
+ # or
360
+ require 'functional/concurrency'
361
+
362
+ pool = Functional::CachedThreadPool.new
363
+
364
+ pool.size #=> 0
365
+ pool.running? #=> true
366
+ pool.status #=> []
367
+
368
+ pool.post(1,2,3){|*args| sleep(10) }
369
+ pool << proc{ sleep(10) }
370
+ pool.size #=> 2
371
+ pool.status #=> [[:working, nil, "sleep"], [:working, nil, "sleep"]]
372
+
373
+ sleep(11)
374
+ pool.status #=> [[:idle, 23, "sleep"], [:idle, 23, "sleep"]]
375
+
376
+ sleep(60)
377
+ pool.size #=> 0
378
+ pool.status #=> []
379
+
380
+ pool.shutdown #=> :shuttingdown
381
+ pool.status #=> []
382
+ pool.wait_for_termination
383
+
384
+ pool.size #=> 0
385
+ pool.status #=> []
386
+ pool.shutdown? #=> true
387
+ ```
388
+
389
+ ## Global Thread Pool
390
+
391
+ For efficiency, of the aforementioned concurrency methods (agents, futures, promises, and
392
+ goroutines) run against a global thread pool. This pool can be directly accessed through the
393
+ `$GLOBAL_THREAD_POOL` global variable. Generally, this pool should not be directly accessed.
394
+ Use the other concurrency features instead.
395
+
396
+ By default the global thread pool is a `CachedThreadPool`. This means it consumes no resources
397
+ unless concurrency functions are called. Most of the time this pool can simply be left alone.
398
+
399
+ ### Changing the Global Thread Pool
400
+
401
+ It is possible to change the global thread pool. Simply assign a new pool to the `$GLOBAL_THREAD_POOL`
402
+ variable:
403
+
404
+ ```ruby
405
+ $GLOBAL_THREAD_POOL = Functional::FixedThreadPool.new(10)
406
+ ```
407
+
408
+ Ideally this should be done at application startup, before any concurrency functions are called.
409
+ If the circumstances warrant the global thread pool can be changed at runtime. Just make sure to
410
+ shutdown the old global thread pool so that no tasks are lost:
411
+
412
+ ```ruby
413
+ $GLOBAL_THREAD_POOL = Functional::FixedThreadPool.new(10)
414
+
415
+ # do stuff...
416
+
417
+ old_global_pool = $GLOBAL_THREAD_POOL
418
+ $GLOBAL_THREAD_POOL = Functional::FixedThreadPool.new(10)
419
+ old_global_pool.shutdown
420
+ ```
421
+
422
+ ### EventMachine
423
+
424
+ The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
425
+ is an awesome library for creating evented applications. EventMachine provides its own thread pool
426
+ and the authors recommend using their pool rather than using Ruby's `Thread`. No sweat,
427
+ `functional-ruby` is fully compatible with EventMachine. Simple require `eventmachine`
428
+ *before* requiring `functional-ruby` then replace the global thread pool with an instance
429
+ of `EventMachineDeferProxy`:
430
+
431
+ ```ruby
432
+ require 'eventmachine' # do this FIRST
433
+ require 'functional/concurrency'
434
+
435
+ $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
436
+ ```
437
+
438
+ ## Copyright
439
+
440
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
441
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
442
+
443
+ ## License
444
+
445
+ Released under the MIT license.
446
+
447
+ http://www.opensource.org/licenses/mit-license.php
448
+
449
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
450
+ > of this software and associated documentation files (the "Software"), to deal
451
+ > in the Software without restriction, including without limitation the rights
452
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
453
+ > copies of the Software, and to permit persons to whom the Software is
454
+ > furnished to do so, subject to the following conditions:
455
+ >
456
+ > The above copyright notice and this permission notice shall be included in
457
+ > all copies or substantial portions of the Software.
458
+ >
459
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
460
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
461
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
462
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
463
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
464
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
465
+ > THE SOFTWARE.
data/md/future.md ADDED
@@ -0,0 +1,32 @@
1
+ # We're sending you back to the future!
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
data/md/obligation.md ADDED
@@ -0,0 +1,32 @@
1
+ # Obligation
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.