fear 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -4
  3. data/.travis.yml +2 -3
  4. data/Appraisals +5 -9
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE.txt +1 -1
  8. data/README.md +255 -85
  9. data/Rakefile +393 -0
  10. data/fear.gemspec +13 -6
  11. data/gemfiles/dry_equalizer_0.1.0.gemfile +1 -0
  12. data/gemfiles/dry_equalizer_0.1.0.gemfile.lock +31 -27
  13. data/gemfiles/dry_equalizer_0.2.1.gemfile +1 -0
  14. data/gemfiles/dry_equalizer_0.2.1.gemfile.lock +31 -27
  15. data/lib/fear/either.rb +49 -14
  16. data/lib/fear/either_pattern_match.rb +48 -0
  17. data/lib/fear/empty_partial_function.rb +36 -0
  18. data/lib/fear/failure.rb +5 -4
  19. data/lib/fear/failure_pattern_match.rb +8 -0
  20. data/lib/fear/for.rb +46 -51
  21. data/lib/fear/left.rb +7 -1
  22. data/lib/fear/left_pattern_match.rb +9 -0
  23. data/lib/fear/none.rb +37 -2
  24. data/lib/fear/none_pattern_match.rb +12 -0
  25. data/lib/fear/option.rb +65 -31
  26. data/lib/fear/option_pattern_match.rb +45 -0
  27. data/lib/fear/partial_function/and_then.rb +48 -0
  28. data/lib/fear/partial_function/any.rb +26 -0
  29. data/lib/fear/partial_function/combined.rb +51 -0
  30. data/lib/fear/partial_function/empty.rb +6 -0
  31. data/lib/fear/partial_function/guard/and.rb +36 -0
  32. data/lib/fear/partial_function/guard/and3.rb +39 -0
  33. data/lib/fear/partial_function/guard/or.rb +36 -0
  34. data/lib/fear/partial_function/guard.rb +90 -0
  35. data/lib/fear/partial_function/lifted.rb +20 -0
  36. data/lib/fear/partial_function/or_else.rb +62 -0
  37. data/lib/fear/partial_function.rb +171 -0
  38. data/lib/fear/partial_function_class.rb +26 -0
  39. data/lib/fear/pattern_match.rb +102 -0
  40. data/lib/fear/pattern_matching_api.rb +110 -0
  41. data/lib/fear/right.rb +7 -1
  42. data/lib/fear/right_biased.rb +2 -12
  43. data/lib/fear/right_pattern_match.rb +9 -0
  44. data/lib/fear/some.rb +5 -2
  45. data/lib/fear/some_pattern_match.rb +11 -0
  46. data/lib/fear/success.rb +5 -4
  47. data/lib/fear/success_pattern_match.rb +10 -0
  48. data/lib/fear/try.rb +56 -16
  49. data/lib/fear/try_pattern_match.rb +28 -0
  50. data/lib/fear/utils.rb +24 -14
  51. data/lib/fear/version.rb +1 -1
  52. data/lib/fear.rb +21 -4
  53. data/spec/fear/either_pattern_match_spec.rb +37 -0
  54. data/spec/fear/failure_spec.rb +41 -3
  55. data/spec/fear/for_spec.rb +17 -29
  56. data/spec/fear/guard_spec.rb +101 -0
  57. data/spec/fear/left_spec.rb +38 -0
  58. data/spec/fear/none_spec.rb +80 -0
  59. data/spec/fear/option_pattern_match_spec.rb +35 -0
  60. data/spec/fear/partial_function/empty_spec.rb +36 -0
  61. data/spec/fear/partial_function_and_then_spec.rb +145 -0
  62. data/spec/fear/partial_function_composition_spec.rb +80 -0
  63. data/spec/fear/partial_function_or_else_spec.rb +274 -0
  64. data/spec/fear/partial_function_spec.rb +165 -0
  65. data/spec/fear/pattern_match_spec.rb +59 -0
  66. data/spec/fear/right_biased/left.rb +1 -6
  67. data/spec/fear/right_biased/right.rb +0 -5
  68. data/spec/fear/right_spec.rb +38 -0
  69. data/spec/fear/some_spec.rb +37 -0
  70. data/spec/fear/success_spec.rb +41 -4
  71. data/spec/fear/try_pattern_match_spec.rb +37 -0
  72. metadata +97 -23
  73. data/lib/fear/for/evaluation_context.rb +0 -91
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f6f57b15284f2ed3034bc697608d9c44e7401b1
4
- data.tar.gz: 439fb988ae4fab755815100e092a65db332e960e
3
+ metadata.gz: f2a2b3d7163ba41625120e4681b75a845e5d9da9
4
+ data.tar.gz: 5d85fb64e778587e9f8f85695acb4f19602ae47e
5
5
  SHA512:
6
- metadata.gz: 8409d00dd720c5dfbd623e73d72abd86ae5c90235e08dd646ad2e9c19b17c502d1a3d0f9a372ae23bb1af1e6cd9571cd425a6c7305e95256746031a6a634c71b
7
- data.tar.gz: 051a50aa71c6fad65b52798ee303f59037c988365a449afa4653afab5f62d53443c31d0d731d07f09a39c564d330cd927243b701e7925aefa913f78ba7fd52fb
6
+ metadata.gz: 99ff3b3886920d253e835b6d1346ff7680f26650a01087cbd83629ec3ba3e7a152ef7714937c653639eef1689dcbac242c7a18cfd26db8d41155f5f4cc9bd80b
7
+ data.tar.gz: 7fcabd7b3cada123a3d2988c4cc4c19bf2af8195e10ff151a886f9a24e1b12dfa73061ca9bc323c056aee9f0be985886b74a02b23a2eeacc10d39e8da2735fb5
data/.rubocop.yml CHANGED
@@ -1,11 +1,11 @@
1
- inherit_gem:
2
- spbtv_code_style: .strict_rubocop.yml
3
-
4
1
  AllCops:
5
2
  Exclude:
6
3
  - 'gemfiles/**/*'
7
4
 
8
- Style/MethodName:
5
+ Naming/MethodName:
6
+ Enabled: false
7
+
8
+ Naming/UncommunicativeMethodParamName:
9
9
  Enabled: false
10
10
 
11
11
  Style/Documentation:
@@ -14,6 +14,32 @@ Style/Documentation:
14
14
  Style/CaseEquality:
15
15
  Enabled: false
16
16
 
17
+ Style/AccessModifierDeclarations:
18
+ EnforcedStyle: inline
19
+
20
+ Style/GuardClause:
21
+ Enabled: false
22
+
23
+ Style/IfUnlessModifier:
24
+ Enabled: false
25
+
26
+ Style/TrailingCommaInArguments:
27
+ EnforcedStyleForMultiline: comma
28
+
29
+ Style/TrailingCommaInArrayLiteral:
30
+ EnforcedStyleForMultiline: comma
31
+
32
+ Style/TrailingCommaInHashLiteral:
33
+ EnforcedStyleForMultiline: comma
34
+
35
+ Style/NumericPredicate:
36
+ Enabled: false
37
+
17
38
  Metrics/BlockLength:
18
39
  Exclude:
19
40
  - spec/**/*
41
+ - Rakefile
42
+ - fear.gemspec
43
+
44
+ Metrics/LineLength:
45
+ Max: 120
data/.travis.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  ---
2
+ cache:
3
+ bundler: true
2
4
  language: ruby
3
5
  rvm:
4
6
  - 2.4.5
@@ -12,6 +14,3 @@ script:
12
14
  gemfile:
13
15
  - gemfiles/dry_equalizer_0.2.1.gemfile
14
16
  - gemfiles/dry_equalizer_0.1.0.gemfile
15
- addons:
16
- code_climate:
17
- repo_token: c326cca5984d0e32d2c6b5d9b985756ee9312f63fc6a9480fc9cfa55c454d68a
data/Appraisals CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'yaml'
2
2
 
3
- ruby_versions = %w(2.4.5 2.5.3 2.6.1)
4
- dry_equalizer_versions = %w(0.1.0 0.2.1)
3
+ ruby_versions = %w[2.4.5 2.5.3 2.6.1]
4
+ dry_equalizer_versions = %w[0.1.0 0.2.1]
5
5
 
6
6
  dry_equalizer_versions.each do |version|
7
7
  appraise "dry-equalizer-#{version}" do
@@ -11,6 +11,9 @@ end
11
11
 
12
12
  Dir.glob('gemfiles/*.gemfile').tap do |gemfiles|
13
13
  travis = ::YAML.dump(
14
+ 'cache' => {
15
+ 'bundler' => true,
16
+ },
14
17
  'language' => 'ruby',
15
18
  'rvm' => ruby_versions,
16
19
  'before_script' => [
@@ -21,13 +24,6 @@ Dir.glob('gemfiles/*.gemfile').tap do |gemfiles|
21
24
  'bundle exec rubocop --fail-level C',
22
25
  ],
23
26
  'gemfile' => gemfiles,
24
- 'addons' => {
25
- 'code_climate' => {
26
- 'repo_token' => 'c326cca5984d0e32d2c6b5d9b985756ee9312f63fc6a9480fc9cfa55c454d68a'
27
- }
28
- }
29
-
30
-
31
27
  )
32
28
 
33
29
  ::File.open('.travis.yml', 'w+') do |file|
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.11.0
2
+
3
+ * Implement pattern matching and partial functions. See [README](https://github.com/bolshakov/fear#pattern-matching-api-documentation) (([@bolshakov][]))
4
+ * `#to_a` method removed ([@bolshakov][])
5
+ * `For` syntax changed. See [diff](https://github.com/bolshakov/fear/pull/22/files#diff-04c6e90faac2675aa89e2176d2eec7d8) ([@bolshakov][])
6
+ * `Fear::None` is singleton object now and could not be instantiated ([@bolshakov][])
7
+
8
+ You have to change all `Fear::None.new` invocations to `Fear::None`.
9
+
1
10
  ## 0.10.0
2
11
 
3
12
  * Test against last supported ruby versions: 2.4.5, 2.5.3, 2.6.1 ([@bolshakov][])
data/Gemfile CHANGED
@@ -4,3 +4,5 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  # gem 'codeclimate-test-reporter', group: :test, require: nil
7
+
8
+ gem 'qo', github: 'baweaver/qo'
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2017 Tema Bolshakov
1
+ Copyright (c) 2015-2019 Tema Bolshakov
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Fear
2
2
  [![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/dbdcfb770918c425e5e4/maintainability)](https://codeclimate.com/github/bolshakov/functional/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/dbdcfb770918c425e5e4/test_coverage)](https://codeclimate.com/github/bolshakov/functional/test_coverage)
5
3
  [![Gem Version](https://badge.fury.io/rb/fear.svg)](https://badge.fury.io/rb/fear)
6
4
 
7
5
  This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
@@ -29,7 +27,7 @@ Or install it yourself as:
29
27
  * [Try](#try-documentation)
30
28
  * [Either](#either-documentation)
31
29
  * [For composition](#for-composition)
32
- * [Pattern Matching](#pattern-matching)
30
+ * [Pattern Matching](#pattern-matching-api-documentation)
33
31
 
34
32
  ### Option ([Documentation](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option))
35
33
 
@@ -50,12 +48,9 @@ having to check for the existence of a value.
50
48
  A less-idiomatic way to use `Option` values is via pattern matching
51
49
 
52
50
  ```ruby
53
- name = Option(params[:name])
54
- case name
55
- when Some
56
- puts name.strip.upcase
57
- when None
58
- puts 'No name value'
51
+ Option(params[:name]).match do |m|
52
+ m.some { |name| name.strip.upcase }
53
+ m.none { 'No name value' }
59
54
  end
60
55
  ```
61
56
 
@@ -76,10 +71,10 @@ Returns the value from this `Some` or evaluates the given default argument if th
76
71
 
77
72
  ```ruby
78
73
  Some(42).get_or_else { 24/2 } #=> 42
79
- None().get_or_else { 24/2 } #=> 12
74
+ None.get_or_else { 24/2 } #=> 12
80
75
 
81
76
  Some(42).get_or_else(12) #=> 42
82
- None().get_or_else(12) #=> 12
77
+ None.get_or_else(12) #=> 12
83
78
  ```
84
79
 
85
80
  #### Option#or_else
@@ -88,8 +83,8 @@ returns self `Some` or the given alternative if this is a `None`.
88
83
 
89
84
  ```ruby
90
85
  Some(42).or_else { Some(21) } #=> Some(42)
91
- None().or_else { Some(21) } #=> Some(21)
92
- None().or_else { None() } #=> None()
86
+ None.or_else { Some(21) } #=> Some(21)
87
+ None.or_else { None } #=> None
93
88
  ```
94
89
 
95
90
  #### Option#inlude?
@@ -99,7 +94,7 @@ Checks if `Option` has an element that is equal (as determined by `==`) to given
99
94
  ```ruby
100
95
  Some(17).include?(17) #=> true
101
96
  Some(17).include?(7) #=> false
102
- None().include?(17) #=> false
97
+ None.include?(17) #=> false
103
98
  ```
104
99
 
105
100
  #### Option#each
@@ -108,7 +103,7 @@ Performs the given block if this is a `Some`.
108
103
 
109
104
  ```ruby
110
105
  Some(17).each { |value| puts value } #=> prints 17
111
- None().each { |value| puts value } #=> does nothing
106
+ None.each { |value| puts value } #=> does nothing
112
107
  ```
113
108
 
114
109
  #### Option#map
@@ -117,7 +112,7 @@ Maps the given block to the value from this `Some` or returns self if this is a
117
112
 
118
113
  ```ruby
119
114
  Some(42).map { |v| v/2 } #=> Some(21)
120
- None().map { |v| v/2 } #=> None()
115
+ None.map { |v| v/2 } #=> None
121
116
  ```
122
117
 
123
118
  #### Option#flat_map
@@ -126,16 +121,7 @@ Returns the given block applied to the value from this `Some` or returns self if
126
121
 
127
122
  ```ruby
128
123
  Some(42).flat_map { |v| Some(v/2) } #=> Some(21)
129
- None().flat_map { |v| Some(v/2) } #=> None()
130
- ```
131
-
132
- #### Option#to_a
133
-
134
- Returns an `Array` containing the `Some` value or an empty `Array` if this is a `None`
135
-
136
- ```ruby
137
- Some(42).to_a #=> [21]
138
- None().to_a #=> []
124
+ None.flat_map { |v| Some(v/2) } #=> None
139
125
  ```
140
126
 
141
127
  #### Option#any?
@@ -145,7 +131,7 @@ Returns `false` if `None` or returns the result of the application of the given
145
131
  ```ruby
146
132
  Some(12).any?( |v| v > 10) #=> true
147
133
  Some(7).any?( |v| v > 10) #=> false
148
- None().any?( |v| v > 10) #=> false
134
+ None.any?( |v| v > 10) #=> false
149
135
  ```
150
136
 
151
137
  #### Option#select
@@ -155,8 +141,8 @@ return `None`.
155
141
 
156
142
  ```ruby
157
143
  Some(42).select { |v| v > 40 } #=> Success(21)
158
- Some(42).select { |v| v < 40 } #=> None()
159
- None().select { |v| v < 40 } #=> None()
144
+ Some(42).select { |v| v < 40 } #=> None
145
+ None.select { |v| v < 40 } #=> None
160
146
  ```
161
147
 
162
148
  #### Option#reject
@@ -166,7 +152,7 @@ Returns `Some` if applying the predicate to this `Option`'s value returns `false
166
152
  ```ruby
167
153
  Some(42).reject { |v| v > 40 } #=> None
168
154
  Some(42).reject { |v| v < 40 } #=> Some(42)
169
- None().reject { |v| v < 40 } #=> None
155
+ None.reject { |v| v < 40 } #=> None
170
156
  ```
171
157
 
172
158
  #### Option#get
@@ -179,7 +165,7 @@ Returns `true` if the `Option` is `None`, `false` otherwise.
179
165
 
180
166
  ```ruby
181
167
  Some(42).empty? #=> false
182
- None().empty? #=> true
168
+ None.empty? #=> true
183
169
  ```
184
170
 
185
171
  @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
@@ -203,11 +189,19 @@ dividend = Try { Integer(params[:dividend]) }
203
189
  divisor = Try { Integer(params[:divisor]) }
204
190
  problem = dividend.flat_map { |x| divisor.map { |y| x / y } }
205
191
 
206
- if problem.success?
207
- puts "Result of #{dividend.get} / #{divisor.get} is: #{problem.get}"
208
- else
209
- puts "You must've divided by zero or entered something wrong. Try again"
210
- puts "Info from the exception: #{problem.exception.message}"
192
+ problem.match |m|
193
+ m.success do |result|
194
+ puts "Result of #{dividend.get} / #{divisor.get} is: #{result}"
195
+ end
196
+
197
+ m.failure(ZeroDivisionError) do
198
+ puts "Division by zero is not allowed"
199
+ end
200
+
201
+ m.failure do |exception|
202
+ puts "You entered something wrong. Try again"
203
+ puts "Info from the exception: #{exception.message}"
204
+ end
211
205
  end
212
206
  ```
213
207
 
@@ -271,22 +265,13 @@ Success(42).flat_map { |v| Success(v/2) } #=> Success(21)
271
265
  Failure(ArgumentError.new).flat_map { |v| Success(v/2) } #=> Failure(ArgumentError.new)
272
266
  ```
273
267
 
274
- #### Try#to_a
275
-
276
- Returns an `Array` containing the `Success` value or an empty `Array` if this is a `Failure`.
277
-
278
- ```ruby
279
- Success(42).to_a #=> [21]
280
- Failure(ArgumentError.new).to_a #=> []
281
- ```
282
-
283
268
  #### Try#to_option
284
269
 
285
270
  Returns an `Some` containing the `Success` value or a `None` if this is a `Failure`.
286
271
 
287
272
  ```ruby
288
273
  Success(42).to_option #=> Some(21)
289
- Failure(ArgumentError.new).to_option #=> None()
274
+ Failure(ArgumentError.new).to_option #=> None
290
275
  ```
291
276
 
292
277
  #### Try#any?
@@ -362,7 +347,7 @@ Success(42).recover_with { |e| Success(e.massage) }
362
347
  #=> Success(42)
363
348
  Failure(ArgumentError.new).recover_with { |e| Success(e.massage) }
364
349
  #=> Success('ArgumentError')
365
- Failure(ArgumentError.new).recover_with { |e| fail }
350
+ Failure(ArgumentError.new).recover_with { |e| raise }
366
351
  #=> Failure(RuntimeError)
367
352
  ```
368
353
 
@@ -375,7 +360,7 @@ Success(42).recover { |e| e.massage }
375
360
  #=> Success(42)
376
361
  Failure(ArgumentError.new).recover { |e| e.massage }
377
362
  #=> Success('ArgumentError')
378
- Failure(ArgumentError.new).recover { |e| fail }
363
+ Failure(ArgumentError.new).recover { |e| raise }
379
364
  #=> Failure(RuntimeError)
380
365
  ```
381
366
 
@@ -410,12 +395,15 @@ rescue ArgumentError
410
395
  Left(in)
411
396
  end
412
397
 
413
- puts(
414
- result.reduce(
415
- -> (x) { "You passed me the String: #{x}" },
416
- -> (x) { "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}" }
417
- )
418
- )
398
+ result.match do |m|
399
+ m.right do |x|
400
+ "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}"
401
+ end
402
+
403
+ m.left do |x|
404
+ "You passed me the String: #{x}"
405
+ end
406
+ end
419
407
  ```
420
408
 
421
409
  Either is right-biased, which means that `Right` is assumed to be the default case to
@@ -481,22 +469,13 @@ Right(42).flat_map { |v| Right(v/2) } #=> Right(21)
481
469
  Left('undefined').flat_map { |v| Right(v/2) } #=> Left('undefined')
482
470
  ```
483
471
 
484
- #### Either#to_a
485
-
486
- Returns an `Array` containing the `Right` value or an empty `Array` if this is a `Left`.
487
-
488
- ```ruby
489
- Right(42).to_a #=> [21]
490
- Left('undefined').to_a #=> []
491
- ```
492
-
493
472
  #### Either#to_option
494
473
 
495
474
  Returns an `Some` containing the `Right` value or a `None` if this is a `Left`.
496
475
 
497
476
  ```ruby
498
477
  Right(42).to_option #=> Some(21)
499
- Left('undefined').to_option #=> None()
478
+ Left('undefined').to_option #=> None
500
479
  ```
501
480
 
502
481
  #### Either#any?
@@ -615,23 +594,32 @@ It supports two such operations - `flat_map` and `map`. Any class providing them
615
594
  is supported by `For`.
616
595
 
617
596
  ```ruby
618
- For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
597
+ For(Some(2), Some(3)) do |a, b|
598
+ a * b
599
+ end #=> Some(6)
619
600
  ```
620
601
 
621
602
  If one of operands is None, the result is None
622
603
 
623
604
  ```ruby
624
- For(a: Some(2), b: None()) { a * b } #=> None()
625
- For(a: None(), b: Some(2)) { a * b } #=> None()
605
+ For(Some(2), None) do |a, b|
606
+ a * b
607
+ end #=> None
608
+
609
+ For(None, Some(2)) do |a, b|
610
+ a * b
611
+ end #=> None
626
612
  ```
627
613
 
628
614
  Lets look at first example:
629
615
 
630
616
  ```ruby
631
- For(a: Some(2), b: Some(3)) { a * b }
617
+ For(Some(2), None) do |a, b|
618
+ a * b
619
+ end #=> None
632
620
  ```
633
621
 
634
- would be translated to:
622
+ it is translated to:
635
623
 
636
624
  ```ruby
637
625
  Some(2).flat_map do |a|
@@ -644,7 +632,7 @@ end
644
632
  It works with arrays as well
645
633
 
646
634
  ```ruby
647
- For(a: [1, 2], b: [2, 3], c: [3, 4]) { a * b * c }
635
+ For([1, 2], [2, 3], [3, 4]) { |a, b, c| a * b * c }
648
636
  #=> [6, 8, 9, 12, 12, 16, 18, 24]
649
637
 
650
638
  ```
@@ -665,8 +653,9 @@ If you pass lambda as a variable value, it would be evaluated
665
653
  only on demand.
666
654
 
667
655
  ```ruby
668
- For(a: -> { None() }, b: -> { fail 'kaboom' } ) { a * b }
669
- #=> None()
656
+ For(proc { None }, proc { raise 'kaboom' } ) do |a, b|
657
+ a * b
658
+ end #=> None
670
659
  ```
671
660
 
672
661
  It does not fail since `b` is not evaluated.
@@ -675,28 +664,209 @@ You can refer to previously defined variables from within lambdas.
675
664
  ```ruby
676
665
  maybe_user = find_user('Paul') #=> <#Option value=<#User ...>>
677
666
 
678
- For(user: maybe_user, birthday: -> { user.birthday }) do
667
+ For(maybe_user, ->(user) { user.birthday }) do |user, birthday|
679
668
  "#{user.name} was born on #{birthday}"
680
669
  end #=> Some('Paul was born on 1987-06-17')
681
670
  ```
682
671
 
683
- ### Pattern Matching
672
+ ### Pattern Matching (API Documentation)
673
+
674
+ Pattern matcher is a combination of partial functions wrapped into nice DSL. Every partial function
675
+ defined on domain described with guard.
676
+
677
+ ```ruby
678
+ pf = Fear.case(Integer) { |x| x / 2 }
679
+ pf.defined_at?(4) #=> true
680
+ pf.defined_at?('Foo') #=> false
681
+ pf.call('Foo') #=> raises Fear::MatchError
682
+ pf.call_or_else('Foo') { 'not a number' } #=> 'not a number'
683
+ pf.call_or_else(4) { 'not a number' } #=> 2
684
+ pf.lift.call('Foo') #=> Fear::None
685
+ pf.lift.call(4) #=> Fear::Some(2)
686
+ ```
687
+
688
+ It uses `#===` method under the hood, so you can pass:
684
689
 
685
- `Option`, `Either`, and `Try` contains enhanced version of `#===` method. It performs matching not
686
- only on container itself, but on enclosed value as well. I'm writing all the options in a one
687
- case statement in sake of simplicity.
690
+ * Class to check kind of an object.
691
+ * Lambda to evaluate it against an object.
692
+ * Any literal, like `4`, `"Foobar"`, etc.
693
+ * Symbol -- it is converted to lambda using `#to_proc` method.
694
+ * Qo matcher -- `m.case(Qo[name: 'John']) { .... }`
688
695
 
696
+ Partial functions may be combined with each other:
697
+
698
+ ```ruby
699
+ is_even = Fear.case(->(arg) { arg % 2 == 0}) { |arg| "#{arg} is even" }
700
+ is_odd = Fear.case(->(arg) { arg % 2 == 1}) { |arg| "#{arg} is odd" }
701
+
702
+ (10..20).map(&is_even.or_else(is_odd))
703
+
704
+ to_integer = Fear.case(String, &:to_i)
705
+ integer_two_times = Fear.case(Integer) { |x| x * 2 }
706
+
707
+ two_times = to_integer.and_then(integer_two_times).or_else(integer_two_times)
708
+ two_times.(4) #=> 8
709
+ two_times.('42') #=> 84
710
+ ```
711
+
712
+ To create custom pattern match use `Fear.match` method and `case` builder to define
713
+ branches. For instance this matcher applies different functions to Integers and Strings
714
+
715
+ ```ruby
716
+ Fear.match(value) do |m|
717
+ m.case(Integer) { |n| "#{n} is a number" }
718
+ m.case(String) { |n| "#{n} is a string" }
719
+ end
720
+ ```
721
+
722
+ if you pass something other than Integer or string, it will raise `Fear::MatchError` error.
723
+ To avoid raising `MatchError`, you can use `else` method. It defines a branch matching
724
+ on any value.
725
+
726
+ ```ruby
727
+ Fear.match(10..20) do |m|
728
+ m.case(Integer) { |n| "#{n} is a number" }
729
+ m.case(String) { |n| "#{n} is a string" }
730
+ m.else { |n| "#{n} is a #{n.class}" }
731
+ end #=> "10..20 is a Range"
732
+ ```
733
+
734
+ You can use anything as a guardian if it responds to `#===` method:
735
+
689
736
  ```ruby
690
- case Some(42)
691
- when Some(42) #=> matches
692
- when Some(41) #=> does not match
693
- when Some(Fixnum) #=> matches
694
- when Some(String) #=> does not match
695
- when Some((40..43)) #=> matches
696
- when Some(-> (x) { x > 40 }) #=> matches
697
- end
737
+ m.case(20..40) { |m| "#{m} is within range" }
738
+ m.case(->(x) { x > 10}) { |m| "#{m} is greater than 10" }
698
739
  ```
699
740
 
741
+ If you pass a Symbol, it will be converted to proc using `#to_proc` method
742
+
743
+ ```ruby
744
+ m.case(:even?) { |x| "#{x} is even" }
745
+ m.case(:odd?) { |x| "#{x} is odd" }
746
+ ```
747
+
748
+ It's also possible to pass several guardians. All should match to pass
749
+
750
+ ```ruby
751
+ m.case(Integer, :even?) { |x| ... }
752
+ m.case(Integer, :odd?) { |x| ... }
753
+ ```
754
+
755
+ It's also possible to create matcher and use it several times:
756
+
757
+ ```ruby
758
+ matcher = Fear.matcher do |m|
759
+ m.case(Integer) { |n| "#{n} is a number" }
760
+ m.case(String) { |n| "#{n} is a string" }
761
+ m.else { |n| "#{n} is a #{n.class}" }
762
+ end
763
+
764
+ matcher.(42) #=> "42 is a number"
765
+ matcher.(10..20) #=> "10..20 is a Range"
766
+ ```
767
+
768
+ Since matcher is just a syntactic sugar for partial functions, you can combine matchers with partial
769
+ functions and each other.
770
+
771
+ ```ruby
772
+ handle_numbers = Fear.case(Integer, &:itself).and_then(
773
+ Fear.matcher do |m|
774
+ m.case(0) { 'zero' }
775
+ m.case(->(n) { n < 10 }) { 'smaller than ten' }
776
+ m.case(->(n) { n > 10 }) { 'bigger than ten' }
777
+ end
778
+ )
779
+
780
+ handle_strings = Fear.case(String, &:itself).and_then(
781
+ Fear.matcher do |m|
782
+ m.case('zero') { 0 }
783
+ m.case('one') { 1 }
784
+ m.else { 'unexpected' }
785
+ end
786
+ )
787
+
788
+ handle = handle_numbers.or_else(handle_strings)
789
+ handle.(0) #=> 'zero'
790
+ handle.(12) #=> 'bigger than ten'
791
+ handle.('one') #=> 1
792
+ ```
793
+
794
+ #### More examples
795
+
796
+ Factorial using pattern matching
797
+
798
+ ```ruby
799
+ factorial = Fear.matcher do |m|
800
+ m.case(->(n) { n <= 1} ) { 1 }
801
+ m.else { |n| n * factorial.(n - 1) }
802
+ end
803
+
804
+ factorial.(10) #=> 3628800
805
+ ```
806
+
807
+ Fibonacci number
808
+
809
+ ```ruby
810
+ fibonnaci = Fear.matcher do |m|
811
+ m.case(0) { 0 }
812
+ m.case(1) { 1 }
813
+ m.case(->(n) { n > 1}) { |n| fibonnaci.(n - 1) + fibonnaci.(n - 2) }
814
+ m.else { raise 'should be positive' }
815
+ end
816
+
817
+ fibonnaci.(10) #=> 55
818
+ ```
819
+
820
+ Binary tree set implemented using pattern matching https://gist.github.com/bolshakov/3c51bbf7be95066d55d6d1ac8c605a1d
821
+
822
+ #### Monads pattern matching
823
+
824
+ You can use `Option#match`, `Either#match`, and `Try#match` method. It performs matching not
825
+ only on container itself, but on enclosed value as well.
826
+
827
+ Pattern match against an `Option`
828
+
829
+ ```ruby
830
+ Some(42).match do |m|
831
+ m.some { |x| x * 2 }
832
+ m.none { 'none' }
833
+ end #=> 84
834
+ ```
835
+
836
+ pattern match on enclosed value
837
+
838
+ ```ruby
839
+ Some(41).match do |m|
840
+ m.some(:even?) { |x| x / 2 }
841
+ m.some(:odd?, ->(v) { v > 0 }) { |x| x * 2 }
842
+ m.none { 'none' }
843
+ end #=> 82
844
+ ```
845
+
846
+ it raises `Fear::MatchError` error if nothing matched. To avoid exception, you can pass `#else` branch
847
+
848
+ ```ruby
849
+ Some(42).match do |m|
850
+ m.some(:odd?) { |x| x * 2 }
851
+ m.else { 'nothing' }
852
+ end #=> nothing
853
+ ```
854
+
855
+ Pattern matching works the similar way for `Either` and `Try` monads.
856
+
857
+ In sake of performance, you may want to generate pattern matching function and reuse it multiple times:
858
+
859
+ ```ruby
860
+ matcher = Option.matcher do |m|
861
+ m.some(42) { 'Yep' }
862
+ m.some { 'Nope' }
863
+ m.none { 'Error' }
864
+ end
865
+
866
+ matcher.(Some(42)) #=> 'Yep'
867
+ matcher.(Some(40)) #=> 'Nope'
868
+ ```
869
+
700
870
  ## Testing
701
871
 
702
872
  To simplify testing, you may use [fear-rspec](https://github.com/bolshakov/fear-rspec) gem. It