fear 0.0.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a628e4319dcc4a672e6a632d6582fdcd9c8bd71
4
- data.tar.gz: 3833ced7c33d30fe1111234549b924449a999032
3
+ metadata.gz: 1fa320f98cb5dc71a7b4e59d7a500c01b729f449
4
+ data.tar.gz: ea1935382f1bed41adab1264490f3346ff7d93c9
5
5
  SHA512:
6
- metadata.gz: 56fb816f68a1a85bba4d8819e714c9669b7d2c9972d35a3b4e0c748e16698e2af16687a71cb8099d843800946da2d097fa71045b9c765f8779ef5863ca0a8021
7
- data.tar.gz: efa4007136b4b74dbdedddfdfe3e9cd3ccfa5edd4d71d6cd6b554ac0b074298076cdbca2ca3b57a95c81fcf79d4974b9d89a059dd4c5f78855770e100a1134a9
6
+ metadata.gz: 49da39785143896e2c6ae73b86765fd7aefdac8603ecbf2bd2d4516cce164dd5946fb9041201f7738eca4fbe8c1e6d78760696a0717d5918c36a7dfe48bfc6ff
7
+ data.tar.gz: 66404d368eb7162f626a4f822b4d4cc0975888f764ffa2bbb31354227a04b795ba8ced9f0ee769880aa6831dc90759f24ea87769a39d88795d8485228e053896
data/.rubocop.yml CHANGED
@@ -4,29 +4,5 @@ inherit_gem:
4
4
  Style/MethodName:
5
5
  Enabled: false
6
6
 
7
- # This configuration was generated by `rubocop --auto-gen-config`
8
- # on 2015-03-09 00:50:30 +0300 using RuboCop version 0.29.1.
9
- # The point is for the user to remove these configuration records
10
- # one by one as the offenses are removed from the code base.
11
- # Note that changes in the inspected code, or installation of new
12
- # versions of RuboCop, may require this file to be generated again.
13
-
14
- # Offense count: 1
15
- # Configuration parameters: CountComments.
16
- Metrics/ClassLength:
17
- Enabled: false
18
- Max: 133
19
-
20
- # Offense count: 1
21
- # Configuration parameters: CountComments.
22
- Metrics/ModuleLength:
23
- Max: 130
24
-
25
- # Offense count: 9
26
7
  Style/Documentation:
27
8
  Enabled: false
28
-
29
- # Offense count: 2
30
- # Configuration parameters: Methods.
31
- Style/SingleLineBlockParams:
32
- Enabled: false
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Fear
2
2
  [![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
3
+ [![Gem Version](https://badge.fury.io/rb/fear.svg)](https://badge.fury.io/rb/fear)
3
4
 
4
- TODO: Write a gem description
5
+ This gem provides `Option`, `Either`, and `Try` monads implemented an idiomatic way.
6
+ It is highly inspired by scala's implementation.
5
7
 
6
8
  ## Installation
7
9
 
@@ -21,7 +23,136 @@ Or install it yourself as:
21
23
 
22
24
  ## Usage
23
25
 
24
- TODO: Write usage instructions here
26
+ ### Option
27
+
28
+ Represents optional values. Instances of `Option` are either an instance of
29
+ `Some` or the object `None`.
30
+
31
+ The most idiomatic way to use an `Option` instance is to treat it
32
+ as a collection and use `map`, `flat_map`, `select`, or `each`:
33
+
34
+ ```ruby
35
+ name = Option(params[:name])
36
+ upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
37
+ puts upper.get_or_else('')
38
+ ```
39
+
40
+ This allows for sophisticated chaining of `Option` values without
41
+ having to check for the existence of a value.
42
+
43
+ See full documentation [Fear::Option](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Option)
44
+
45
+ ### Try
46
+
47
+ The `Try` represents a computation that may either result in an exception,
48
+ or return a successfully computed value. Instances of `Try`, are either
49
+ an instance of `Success` or `Failure`.
50
+
51
+ For example, `Try` can be used to perform division on a user-defined input,
52
+ without the need to do explicit exception-handling in all of the places
53
+ that an exception might occur.
54
+
55
+ ```ruby
56
+ dividend = Try { Integer(params[:dividend]) }
57
+ divisor = Try { Integer(params[:divisor]) }
58
+
59
+ problem = dividend.flat_map { |x| divisor.map { |y| x / y }
60
+
61
+ if problem.success?
62
+ puts "Result of #{dividend.get} / #{divisor.get} is: #{problem.get}"
63
+ else
64
+ puts "You must've divided by zero or entered something wrong. Try again"
65
+ puts "Info from the exception: #{problem.exception.message}"
66
+ end
67
+ ```
68
+
69
+ See full documentation [Fear::Try](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Try)
70
+
71
+ ### Either
72
+
73
+ Represents a value of one of two possible types (a disjoint union.)
74
+ An instance of `Either` is either an instance of `Left` or `Right`.
75
+
76
+ A common use of `Either` is as an alternative to `Option` for dealing
77
+ with possible missing values. In this usage, `None` is replaced
78
+ with a `Left` which can contain useful information.
79
+ `Right` takes the place of `Some`. Convention dictates
80
+ that `Left` is used for failure and `Right` is used for success.
81
+
82
+ For example, you could use `Either<String, Fixnum>` to select whether a
83
+ received input is a `String` or an `Fixnum`.
84
+
85
+ ```ruby
86
+ input = Readline.readline('Type Either a string or an Int: ', true)
87
+ result = begin
88
+ Right(Integer(input))
89
+ rescue ArgumentError
90
+ Left(input)
91
+ end
92
+
93
+ puts(
94
+ result.reduce(
95
+ -> (x) { "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}" },
96
+ -> (x) { "You passed me the String: #{x}" }
97
+ )
98
+ )
99
+ ```
100
+
101
+ See full documentation [Fear::Either](http://www.rubydoc.info/github/bolshakov/fear/master/Fear/Either)
102
+
103
+ ### For composition
104
+
105
+ Provides syntactic sugar for composition of multiple monadic operations.
106
+ It supports two such operations - `flat_map` and `map`. Any class providing them
107
+ is supported by `For`.
108
+
109
+ ```ruby
110
+ For(a: Some(2), b: Some(3)) do
111
+ a * b
112
+ end #=> Some(6)
113
+ ```
114
+
115
+ It would be translated to
116
+
117
+ ```ruby
118
+ Some(2).flat_map do |a|
119
+ Some(3).map do |b|
120
+ a * b
121
+ end
122
+ end
123
+ ```
124
+
125
+ If one of operands is None, the result is None
126
+
127
+ ```ruby
128
+ For(a: Some(2), b: None()) do
129
+ a * b
130
+ end #=> None()
131
+
132
+ For(a: None(), b: Some(2)) do
133
+ a * b
134
+ end #=> None()
135
+ ```
136
+
137
+ `For` works with arrays as well
138
+
139
+ ```ruby
140
+ For(a: [1, 2], b: [2, 3], c: [3, 4]) do
141
+ a * b * c
142
+ end #=> [6, 8, 9, 12, 12, 16, 18, 24]
143
+ ```
144
+
145
+ would be translated to:
146
+
147
+ ```ruby
148
+ [1, 2].flat_map do |a|
149
+ [2, 3].flat_map do |b|
150
+ [3, 4].map do |c|
151
+ a * b * c
152
+ end
153
+ end
154
+ end
155
+ ```
25
156
 
26
157
  ## Contributing
27
158
 
data/lib/fear/either.rb CHANGED
@@ -8,7 +8,7 @@ module Fear
8
8
  # `Right` takes the place of `Some`. Convention dictates
9
9
  # that `Left` is used for failure and `Right` is used for success.
10
10
  #
11
- # For example, you could use `Either<String, Fixnum>` to detect whether a
11
+ # For example, you could use `Either<String, Fixnum>` to select whether a
12
12
  # received input is a `String` or an `Fixnum`.
13
13
  #
14
14
  # @example
@@ -26,7 +26,7 @@ module Fear
26
26
  # )
27
27
  # )
28
28
  #
29
- # `Either` is right-biased, which means that `Right` is assumed to be the default case to
29
+ # Either is right-biased, which means that `Right` is assumed to be the default case to
30
30
  # operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
31
31
  # unchanged:
32
32
  #
@@ -63,11 +63,11 @@ module Fear
63
63
  # Right(12).flat_map { |x| Left('ruby') } #=> Left('ruby')
64
64
  # Left(12).flat_map { |x| Left('ruby') } #=> Left(12)
65
65
  #
66
- # @example #detect
67
- # Right(12).detect(-1, &:even?) #=> Right(12))
68
- # Right(7).detect(-1, &:even?) #=> Left(-1)
69
- # Left(12).detect(-1, &:even?) #=> Left(-1)
70
- # Left(12).detect(-> { -1 }, &:even?) #=> Left(-1)
66
+ # @example #select
67
+ # Right(12).select(-1, &:even?) #=> Right(12))
68
+ # Right(7).select(-1, &:even?) #=> Left(-1)
69
+ # Left(12).select(-1, &:even?) #=> Left(-1)
70
+ # Left(12).select(-> { -1 }, &:even?) #=> Left(-1)
71
71
  #
72
72
  # @example #to_a
73
73
  # Right(12).to_a #=> [12]
@@ -111,7 +111,6 @@ module Fear
111
111
  #
112
112
  module Either
113
113
  include Dry::Equalizer(:value)
114
- include Fear
115
114
 
116
115
  def left_class
117
116
  Left
@@ -125,6 +124,20 @@ module Fear
125
124
  @value = value
126
125
  end
127
126
 
127
+ # @return [Boolean]
128
+ def right?
129
+ is_a?(Right)
130
+ end
131
+
132
+ alias success? right?
133
+
134
+ # @return [Boolean]
135
+ def left?
136
+ !right?
137
+ end
138
+
139
+ alias failure? left?
140
+
128
141
  attr_reader :value
129
142
  protected :value
130
143
 
data/lib/fear/failure.rb CHANGED
@@ -32,8 +32,7 @@ module Fear
32
32
  end
33
33
 
34
34
  # @return [Failure] self
35
- # TODO: rename to select
36
- def detect
35
+ def select
37
36
  self
38
37
  end
39
38
 
data/lib/fear/left.rb CHANGED
@@ -8,7 +8,7 @@ module Fear
8
8
  # @param default [Proc, any]
9
9
  # @return [Either]
10
10
  #
11
- def detect(default)
11
+ def select(default)
12
12
  Left.new(Utils.return_or_call_proc(default))
13
13
  end
14
14
 
data/lib/fear/none.rb CHANGED
@@ -8,7 +8,7 @@ module Fear
8
8
  #
9
9
  # @return [None]
10
10
  #
11
- def detect(*)
11
+ def select(*)
12
12
  self
13
13
  end
14
14
 
data/lib/fear/option.rb CHANGED
@@ -2,20 +2,15 @@ module Fear
2
2
  # Represents optional values. Instances of `Option`
3
3
  # are either an instance of `Some` or the object `None`.
4
4
  #
5
- # The most idiomatic way to use an `Option` instance is to treat it
6
- # as a collection or monad and use `map`, `flat_map`, `detect`, or `each`:
7
- #
8
- # @example
5
+ # @example The most idiomatic way to use an `Option` instance is to treat it as a collection
9
6
  # name = Option(params[:name])
10
- # upper = name.map(&:strip).detect { |n| n.length != 0 }.map(&:upcase)
7
+ # upper = name.map(&:strip).select { |n| n.length != 0 }.map(&:upcase)
11
8
  # puts upper.get_or_else('')
12
9
  #
13
10
  # This allows for sophisticated chaining of `Option` values without
14
11
  # having to check for the existence of a value.
15
12
  #
16
- # A less-idiomatic way to use `Option` values is via pattern matching:
17
- #
18
- # @example
13
+ # @example A less-idiomatic way to use `Option` values is via pattern matching
19
14
  # name = Option(params[:name])
20
15
  # case name
21
16
  # when Some
@@ -24,9 +19,7 @@ module Fear
24
19
  # puts 'No name value'
25
20
  # end
26
21
  #
27
- # or manually checking for non emptiness:
28
- #
29
- # @example
22
+ # @example or manually checking for non emptiness
30
23
  # name = Option(params[:name])
31
24
  # if name.empty?
32
25
  # puts 'No name value'
@@ -34,17 +27,17 @@ module Fear
34
27
  # puts name.strip.upcase
35
28
  # end
36
29
  #
37
- # @example #detect
38
- # User.find(params[:id]).detect do |user|
30
+ # @example #select
31
+ # User.find(params[:id]).select do |user|
39
32
  # user.posts.count > 0
40
33
  # end #=> Some(User)
41
34
  #
42
- # User.find(params[:id]).detect do |user|
35
+ # User.find(params[:id]).select do |user|
43
36
  # user.posts.count > 0
44
37
  # end #=> None
45
38
  #
46
39
  # User.find(params[:id])
47
- # .detect(&:confirmed?)
40
+ # .select(&:confirmed?)
48
41
  # .map(&:posts)
49
42
  # .inject(0, &:count)
50
43
  #
data/lib/fear/right.rb CHANGED
@@ -9,7 +9,7 @@ module Fear
9
9
  # @param default [Proc, any]
10
10
  # @return [Either]
11
11
  #
12
- def detect(default)
12
+ def select(default)
13
13
  if yield(value)
14
14
  self
15
15
  else
@@ -17,7 +17,7 @@ module Fear
17
17
  end
18
18
 
19
19
  # Ensures that returned value either left, or right.
20
- def detect(*)
20
+ def select(*)
21
21
  super.tap do |result|
22
22
  Utils.assert_type!(result, left_class, right_class)
23
23
  end
data/lib/fear/some.rb CHANGED
@@ -20,7 +20,7 @@ module Fear
20
20
  # applying the `predicate` to this option's value
21
21
  # returns true. Otherwise, return `None`.
22
22
  #
23
- def detect
23
+ def select
24
24
  if yield(value)
25
25
  self
26
26
  else
data/lib/fear/success.rb CHANGED
@@ -42,7 +42,7 @@ module Fear
42
42
  # @yieldreturn [Boolean]
43
43
  # @return [Try]
44
44
  #
45
- def detect
45
+ def select
46
46
  if yield(value)
47
47
  self
48
48
  else
data/lib/fear/try.rb CHANGED
@@ -11,8 +11,8 @@ module Fear
11
11
  # might occur.
12
12
  #
13
13
  # @example
14
- # dividend = Try { params[:dividend].to_i }
15
- # divisor = Try { params[:divisor].to_i }
14
+ # dividend = Try { Integer(params[:dividend]) }
15
+ # divisor = Try { Integer(params[:divisor]) }
16
16
  # problem = dividend.flat_map { |x| divisor.map { |y| x / y }
17
17
  #
18
18
  # if problem.success?
@@ -53,12 +53,12 @@ module Fear
53
53
  # Success(42).map { |v| v/2 } #=> Success(21)
54
54
  # Failure(ArgumentError.new).map { |v| v/2 } #=> Failure(ArgumentError.new)
55
55
  #
56
- # @example #detect
57
- # Success(42).detect { |v| v > 40 }
56
+ # @example #select
57
+ # Success(42).select { |v| v > 40 }
58
58
  # #=> Success(21)
59
- # Success(42).detect { |v| v < 40 }
59
+ # Success(42).select { |v| v < 40 }
60
60
  # #=> Failure(NoSuchElementError.new("Predicate does not hold for 42"))
61
- # Failure(ArgumentError.new).detect { |v| v < 40 }
61
+ # Failure(ArgumentError.new).select { |v| v < 40 }
62
62
  # #=> Failure(ArgumentError.new)
63
63
  #
64
64
  # @example #recover_with
data/lib/fear/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fear
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
@@ -33,8 +33,8 @@ RSpec.describe Fear::Failure do
33
33
  it { is_expected.to eq(failure) }
34
34
  end
35
35
 
36
- describe '#detect' do
37
- subject { failure.detect { |v| v == 'value' } }
36
+ describe '#select' do
37
+ subject { failure.select { |v| v == 'value' } }
38
38
  it { is_expected.to eq(failure) }
39
39
  end
40
40
 
@@ -5,9 +5,21 @@ RSpec.describe Fear::Left do
5
5
  let(:left) { described_class.new('value') }
6
6
  end
7
7
 
8
- describe '#detect' do
8
+ let(:left) { described_class.new('value') }
9
+
10
+ describe '#right?' do
11
+ subject { left }
12
+ it { is_expected.not_to be_right }
13
+ end
14
+
15
+ describe '#left?' do
16
+ subject { left }
17
+ it { is_expected.to be_left }
18
+ end
19
+
20
+ describe '#select' do
9
21
  subject do
10
- described_class.new('value').detect(default) { |v| v == 'value' }
22
+ left.select(default) { |v| v == 'value' }
11
23
  end
12
24
 
13
25
  context 'proc default' do
@@ -28,13 +40,13 @@ RSpec.describe Fear::Left do
28
40
  end
29
41
 
30
42
  describe '#swap' do
31
- subject { described_class.new('value').swap }
43
+ subject { left.swap }
32
44
  it { is_expected.to eq(Right('value')) }
33
45
  end
34
46
 
35
47
  describe '#reduce' do
36
48
  subject do
37
- described_class.new('value').reduce(
49
+ left.reduce(
38
50
  ->(left) { "Left: #{left}" },
39
51
  ->(right) { "Right: #{right}" },
40
52
  )
@@ -69,7 +81,7 @@ RSpec.describe Fear::Left do
69
81
  end
70
82
 
71
83
  context 'value is not Either' do
72
- subject { proc { described_class.new('error').join_left } }
84
+ subject { proc { left.join_left } }
73
85
 
74
86
  it 'fails with type error' do
75
87
  is_expected.to raise_error(TypeError)
@@ -19,8 +19,8 @@ RSpec.describe Fear::None do
19
19
  expect(result).to eq nil
20
20
  end
21
21
 
22
- describe '#detect' do
23
- subject { none.detect { |value| value > 42 } }
22
+ describe '#select' do
23
+ subject { none.select { |value| value > 42 } }
24
24
 
25
25
  it 'always return None' do
26
26
  is_expected.to eq(None())
@@ -5,8 +5,18 @@ RSpec.describe Fear::Right do
5
5
 
6
6
  let(:right) { described_class.new('value') }
7
7
 
8
- describe '#detect' do
9
- subject { right.detect(default, &predicate) }
8
+ describe '#right?' do
9
+ subject { right }
10
+ it { is_expected.to be_right }
11
+ end
12
+
13
+ describe '#left?' do
14
+ subject { right }
15
+ it { is_expected.not_to be_left }
16
+ end
17
+
18
+ describe '#select' do
19
+ subject { right.select(default, &predicate) }
10
20
 
11
21
  context 'predicate evaluates to true' do
12
22
  let(:predicate) { ->(v) { v == 'value' } }
@@ -28,13 +38,13 @@ RSpec.describe Fear::Right do
28
38
  end
29
39
 
30
40
  describe '#swap' do
31
- subject { described_class.new('value').swap }
41
+ subject { right.swap }
32
42
  it { is_expected.to eq(Fear::Left.new('value')) }
33
43
  end
34
44
 
35
45
  describe '#reduce' do
36
46
  subject do
37
- described_class.new('value').reduce(
47
+ right.reduce(
38
48
  ->(left) { "Left: #{left}" },
39
49
  ->(right) { "Right: #{right}" },
40
50
  )
@@ -8,8 +8,8 @@ RSpec.describe Fear::Some do
8
8
  subject(:some) { Some(value) }
9
9
  let(:value) { 42 }
10
10
 
11
- describe '#detect' do
12
- subject { some.detect(&predicate) }
11
+ describe '#select' do
12
+ subject { some.select(&predicate) }
13
13
 
14
14
  context 'predicate evaluates to true' do
15
15
  let(:predicate) { ->(v) { v > 40 } }
@@ -54,19 +54,19 @@ RSpec.describe Fear::Success do
54
54
  end
55
55
  end
56
56
 
57
- describe '#detect' do
57
+ describe '#select' do
58
58
  context 'predicate holds for value' do
59
- subject { success.detect { |v| v == 'value' } }
59
+ subject { success.select { |v| v == 'value' } }
60
60
  it { is_expected.to eq(success) }
61
61
  end
62
62
 
63
63
  context 'predicate does not hold for value' do
64
- subject { proc { success.detect { |v| v != 'value' }.get } }
64
+ subject { proc { success.select { |v| v != 'value' }.get } }
65
65
  it { is_expected.to raise_error(Fear::NoSuchElementError, 'Predicate does not hold for `value`') }
66
66
  end
67
67
 
68
68
  context 'predicate fails with error' do
69
- subject { proc { success.detect { fail 'foo' }.get } }
69
+ subject { proc { success.select { fail 'foo' }.get } }
70
70
  it { is_expected.to raise_error(RuntimeError, 'foo') }
71
71
  end
72
72
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fear
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tema Bolshakov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-08 00:00:00.000000000 Z
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-equalizer