fear 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0a628e4319dcc4a672e6a632d6582fdcd9c8bd71
4
+ data.tar.gz: 3833ced7c33d30fe1111234549b924449a999032
5
+ SHA512:
6
+ metadata.gz: 56fb816f68a1a85bba4d8819e714c9669b7d2c9972d35a3b4e0c748e16698e2af16687a71cb8099d843800946da2d097fa71045b9c765f8779ef5863ca0a8021
7
+ data.tar.gz: efa4007136b4b74dbdedddfdfe3e9cd3ccfa5edd4d71d6cd6b554ac0b074298076cdbca2ca3b57a95c81fcf79d4974b9d89a059dd4c5f78855770e100a1134a9
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ inherit_gem:
2
+ spbtv_code_style: .strict_rubocop.yml
3
+
4
+ Style/MethodName:
5
+ Enabled: false
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
+ Style/Documentation:
27
+ Enabled: false
28
+
29
+ # Offense count: 2
30
+ # Configuration parameters: Methods.
31
+ Style/SingleLineBlockParams:
32
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ script:
5
+ - rubocop -D
6
+ - bundle exec rspec spec
7
+ env: CODECLIMATE_REPO_TOKEN=548312a77df2507c49494d3c6e78cc62631517c39440644c38217d9be1f47e26
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in functional.gemspec
4
+ gemspec
5
+
6
+ # gem 'codeclimate-test-reporter', group: :test, require: nil
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Fear
2
+ [![Build Status](https://travis-ci.org/bolshakov/fear.svg?branch=master)](https://travis-ci.org/bolshakov/fear)
3
+
4
+ TODO: Write a gem description
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'fear'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install fear
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it ( https://github.com/bolshakov/fear/fork )
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/fear.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'fear/version'
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'fear'
9
+ spec.version = Fear::VERSION
10
+ spec.authors = ['Tema Bolshakov']
11
+ spec.email = ['abolshakov@spbtv.com']
12
+ spec.summary = "%q{Ruby port of some Scala's monads.}"
13
+ spec.description = "Ruby port of some Scala's monads."
14
+ spec.homepage = 'https://github.com/bolshakov/functional'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin\/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^spec\/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_runtime_dependency 'dry-equalizer', '0.2.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.1'
27
+ spec.add_development_dependency 'spbtv_code_style'
28
+ spec.add_development_dependency 'rubocop-rspec'
29
+ end
@@ -0,0 +1,145 @@
1
+ module Fear
2
+ # Represents a value of one of two possible types (a disjoint union.)
3
+ # An instance of `Either` is either an instance of `Left` or `Right`.
4
+ #
5
+ # A common use of `Either` is as an alternative to `Option` for dealing
6
+ # with possible missing values. In this usage, `None` is replaced
7
+ # with a `Left` which can contain useful information.
8
+ # `Right` takes the place of `Some`. Convention dictates
9
+ # that `Left` is used for failure and `Right` is used for success.
10
+ #
11
+ # For example, you could use `Either<String, Fixnum>` to detect whether a
12
+ # received input is a `String` or an `Fixnum`.
13
+ #
14
+ # @example
15
+ # in = Readline.readline('Type Either a string or an Int: ', true)
16
+ # result = begin
17
+ # Right(Integer(in))
18
+ # rescue ArgumentError
19
+ # Left(in)
20
+ # end
21
+ #
22
+ # puts(
23
+ # result.reduce(
24
+ # -> (x) { "You passed me the Int: #{x}, which I will increment. #{x} + 1 = #{x+1}" },
25
+ # -> (x) { "You passed me the String: #{x}" }
26
+ # )
27
+ # )
28
+ #
29
+ # `Either` is right-biased, which means that `Right` is assumed to be the default case to
30
+ # operate on. If it is `Left`, operations like `#map`, `#flat_map`, ... return the `Left` value
31
+ # unchanged:
32
+ #
33
+ # @example #map
34
+ # Right(12).map { |_| _ * 2) #=> Right(24)
35
+ # Left(23).map { |_| _ * 2) #=> Left(23)
36
+ #
37
+ # @example #get_or_else
38
+ # Right(12).get_or_else(17) #=> 12
39
+ # Left(12).get_or_else(17) #=> 17
40
+ #
41
+ # Right(12).get_or_else { 17 } #=> 12
42
+ # Left(12).get_or_else { 17 } #=> 17
43
+ #
44
+ # @example #include?
45
+ # # Returns true because value of Right is "something" which equals "something".
46
+ # Right("something").include?("something") #= true
47
+ #
48
+ # # Returns false because value of Right is "something" which does not equal "anything".
49
+ # Right("something").include?("anything") #=> false
50
+ #
51
+ # # Returns false because there is no value for Right.
52
+ # Left("something").include?("something") #=> false
53
+ #
54
+ # @example #each
55
+ # Right(12).each { |x| puts x } # prints "12"
56
+ # Left(12).each { |x| puts x } # doesn't print
57
+ #
58
+ # @example #map
59
+ # Right('ruby').map(&:length) #=> Right(4)
60
+ # Left('ruby').map(&:length) #=> Left('ruby')
61
+ #
62
+ # @example #flat_map
63
+ # Right(12).flat_map { |x| Left('ruby') } #=> Left('ruby')
64
+ # Left(12).flat_map { |x| Left('ruby') } #=> Left(12)
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)
71
+ #
72
+ # @example #to_a
73
+ # Right(12).to_a #=> [12]
74
+ # Left(12).to_a #=> []
75
+ #
76
+ # @example #to_option
77
+ # Right(12).to_option #=> Some(12)
78
+ # Left(12).to_option #=> None()
79
+ #
80
+ # @example #any?
81
+ # Right(12).any? { |v| v > 10 } #=> true
82
+ # Right(7).any? { |v| v > 10 } #=> false
83
+ # Left(12).any? { |v| v > 10 } #=> false
84
+ #
85
+ # @example #swap
86
+ # left = Left("left")
87
+ # right = left.swap #=> Right("left")
88
+ #
89
+ # @example #reduce
90
+ # result = possibly_failing_operation()
91
+ # log(
92
+ # result.reduce(
93
+ # ->(ex) { "Operation failed with #{ex}" },
94
+ # ->(v) { "Operation produced value: #{v}" },
95
+ # )
96
+ # )
97
+ #
98
+ # @example #join_right
99
+ # Right(Right(12)).join_right #=> Right(12)
100
+ # Right(Left("flower")).join_right #=> Left("flower")
101
+ # Left("flower").join_right #=> Left("flower")
102
+ # Left(Right("flower")).join_right #=> Left(Right("flower"))
103
+ #
104
+ # @example #join_left
105
+ # Left(Right("flower")).join_left #=> Right("flower")
106
+ # Left(Left(12)).join_left #=> Left(12)
107
+ # Right("daisy").join_left #=> Right("daisy")
108
+ # Right(Left("daisy")).join_left #=> Right(Left("daisy"))
109
+ #
110
+ # @see https://github.com/scala/scala/blob/2.12.x/src/library/scala/util/Either.scala
111
+ #
112
+ module Either
113
+ include Dry::Equalizer(:value)
114
+ include Fear
115
+
116
+ def left_class
117
+ Left
118
+ end
119
+
120
+ def right_class
121
+ Right
122
+ end
123
+
124
+ def initialize(value)
125
+ @value = value
126
+ end
127
+
128
+ attr_reader :value
129
+ protected :value
130
+
131
+ module Mixin
132
+ # @param [any]
133
+ # @return [Left]
134
+ def Left(value)
135
+ Left.new(value)
136
+ end
137
+
138
+ # @param [any]
139
+ # @return [Right]
140
+ def Right(value)
141
+ Right.new(value)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,65 @@
1
+ module Fear
2
+ class Failure
3
+ include Try
4
+ include Dry::Equalizer(:value)
5
+ include RightBiased::Left
6
+
7
+ def initialize(exception)
8
+ @value = exception
9
+ end
10
+
11
+ attr_reader :value
12
+ protected :value
13
+
14
+ def success?
15
+ false
16
+ end
17
+
18
+ def get
19
+ fail value
20
+ end
21
+
22
+ # @return [Try] of calling block
23
+ def or_else
24
+ Success.new(yield)
25
+ rescue => error
26
+ Failure.new(error)
27
+ end
28
+
29
+ # @return [Failure] self
30
+ def flatten
31
+ self
32
+ end
33
+
34
+ # @return [Failure] self
35
+ # TODO: rename to select
36
+ def detect
37
+ self
38
+ end
39
+
40
+ # Applies the given block to exception.
41
+ # This is like `flat_map` for the exception.
42
+ #
43
+ # @yieldparam [Exception]
44
+ # @yieldreturn [Try]
45
+ # @return [Try]
46
+ #
47
+ def recover_with
48
+ yield(value).tap do |result|
49
+ Utils.assert_type!(result, Success, Failure)
50
+ end
51
+ rescue => error
52
+ Failure.new(error)
53
+ end
54
+
55
+ # Applies the given block to exception.
56
+ # This is like map for the exception.
57
+ # @return [Try]
58
+ #
59
+ def recover
60
+ Success.new(yield(value))
61
+ rescue => error
62
+ Failure.new(error)
63
+ end
64
+ end
65
+ end
data/lib/fear/for.rb ADDED
@@ -0,0 +1,104 @@
1
+ module Fear
2
+ # This class provides syntactic sugar for composition of
3
+ # multiple monadic operations. It supports two such
4
+ # operations - `flat_map` and `map`. Any class providing them
5
+ # is supported by `For`.
6
+ #
7
+ # @example Option
8
+ # For(a: Some(2), b: Some(3)) { a * b } #=> Some(6)
9
+ # # If one of operands is None, the result is None
10
+ # For(a: Some(2), b: None()) { a * b } #=> None()
11
+ # For(a: None(), b: Some(2)) { a * b } #=> None()
12
+ #
13
+ # Lets look at first example:
14
+ #
15
+ # For(a: Some(2), b: Some(3)) { a * b }
16
+ #
17
+ # would be translated to:
18
+ #
19
+ # Some(2).flat_map do |a|
20
+ # Some(3).map do |b|
21
+ # a * b
22
+ # end
23
+ # end
24
+ #
25
+ # It works with arrays as well
26
+ #
27
+ # For(a: [1, 2], b: [2, 3], c: [3, 4]) { a * b * c }
28
+ # #=> [6, 8, 9, 12, 12, 16, 18, 24]
29
+ #
30
+ # would be translated to:
31
+ #
32
+ # [1, 2].flat_map do |a|
33
+ # [2, 3].flat_map do |b|
34
+ # [3, 4].map do |c|
35
+ # a * b * c
36
+ # end
37
+ # end
38
+ # end
39
+ #
40
+ class For
41
+ # Context of block evaluation. It respond to passed locals.
42
+ #
43
+ # @example
44
+ # context = EvaluationContext.new(foo: 'bar')
45
+ # context.foo #=> 'bar'
46
+ #
47
+ class EvaluationContext < BasicObject
48
+ # @param locals [Hash{Symbol => any}]
49
+ #
50
+ def initialize(locals)
51
+ @locals = locals
52
+ end
53
+
54
+ def method_missing(name, *args, &block)
55
+ if @locals.include?(name) && args.empty? && block.nil?
56
+ @locals[name]
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def respond_to_missing?(name, _)
63
+ @locals.key?(name)
64
+ end
65
+ end
66
+ private_constant(:EvaluationContext)
67
+
68
+ # @param variables [Hash{Symbol => any}]
69
+ #
70
+ def initialize(**variables)
71
+ @variables = variables
72
+ end
73
+ attr_reader :variables
74
+
75
+ def call(&block)
76
+ variable_name_and_monad, *tail = *variables
77
+ execute({}, *variable_name_and_monad, tail, &block)
78
+ end
79
+
80
+ private
81
+
82
+ def execute(locals, variable_name, monad, monads, &block)
83
+ if monads.empty?
84
+ monad.map do |value|
85
+ EvaluationContext.new(locals.merge(variable_name => value)).instance_eval(&block)
86
+ end
87
+ else
88
+ monad.flat_map do |value|
89
+ variable_name_and_monad, *tail = *monads
90
+ execute(locals.merge(variable_name => value), *variable_name_and_monad, tail, &block)
91
+ end
92
+ end
93
+ end
94
+
95
+ module Mixin
96
+ # @param locals [Hash{Symbol => {#map, #flat_map}}]
97
+ # @return [{#map, #flat_map}]
98
+ #
99
+ def For(**locals, &block)
100
+ For.new(**locals).call(&block)
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/fear/left.rb ADDED
@@ -0,0 +1,52 @@
1
+ module Fear
2
+ class Left
3
+ include Either
4
+ include RightBiased::Left
5
+
6
+ # Returns `Left` or `default`.
7
+ #
8
+ # @param default [Proc, any]
9
+ # @return [Either]
10
+ #
11
+ def detect(default)
12
+ Left.new(Utils.return_or_call_proc(default))
13
+ end
14
+
15
+ # @return [Right] value in `Right`
16
+ #
17
+ def swap
18
+ Right.new(value)
19
+ end
20
+
21
+ # @param reduce_left [Proc] the function to apply if this is a `Left`
22
+ # @return [any] Applies `reduce_left` to the value.
23
+ #
24
+ def reduce(reduce_left, _)
25
+ reduce_left.call(value)
26
+ end
27
+
28
+ # Joins an `Either` through `Right`.
29
+ #
30
+ # @return [self]
31
+ #
32
+ def join_right
33
+ self
34
+ end
35
+
36
+ # Joins an `Either` through `Left`.
37
+ #
38
+ # This method requires that the left side of this `Either` is itself an
39
+ # Either type.
40
+ #
41
+ # This method, and `join_right`, are analogous to `Option#flatten`
42
+ #
43
+ # @return [Either]
44
+ # @raise [TypeError] if it does not contain `Either`.
45
+ #
46
+ def join_left
47
+ value.tap do |v|
48
+ Utils.assert_type!(v, Either)
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/fear/none.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Fear
2
+ class None
3
+ include Option
4
+ include Dry::Equalizer()
5
+ include RightBiased::Left
6
+
7
+ # Ignores the given block and return self.
8
+ #
9
+ # @return [None]
10
+ #
11
+ def detect(*)
12
+ self
13
+ end
14
+
15
+ # Ignores the given block and return self.
16
+ #
17
+ # @return [None]
18
+ #
19
+ def reject(*)
20
+ self
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,112 @@
1
+ module Fear
2
+ # Represents optional values. Instances of `Option`
3
+ # are either an instance of `Some` or the object `None`.
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
9
+ # name = Option(params[:name])
10
+ # upper = name.map(&:strip).detect { |n| n.length != 0 }.map(&:upcase)
11
+ # puts upper.get_or_else('')
12
+ #
13
+ # This allows for sophisticated chaining of `Option` values without
14
+ # having to check for the existence of a value.
15
+ #
16
+ # A less-idiomatic way to use `Option` values is via pattern matching:
17
+ #
18
+ # @example
19
+ # name = Option(params[:name])
20
+ # case name
21
+ # when Some
22
+ # puts name.strip.upcase
23
+ # when None
24
+ # puts 'No name value'
25
+ # end
26
+ #
27
+ # or manually checking for non emptiness:
28
+ #
29
+ # @example
30
+ # name = Option(params[:name])
31
+ # if name.empty?
32
+ # puts 'No name value'
33
+ # else
34
+ # puts name.strip.upcase
35
+ # end
36
+ #
37
+ # @example #detect
38
+ # User.find(params[:id]).detect do |user|
39
+ # user.posts.count > 0
40
+ # end #=> Some(User)
41
+ #
42
+ # User.find(params[:id]).detect do |user|
43
+ # user.posts.count > 0
44
+ # end #=> None
45
+ #
46
+ # User.find(params[:id])
47
+ # .detect(&:confirmed?)
48
+ # .map(&:posts)
49
+ # .inject(0, &:count)
50
+ #
51
+ # @example #reject
52
+ # Some(42).reject { |v| v > 0 } #=> None
53
+ # Some(42).reject { |v| v < 0 } #=> Some(42)
54
+ # None().reject { |v| v > 0 } #=> None
55
+ #
56
+ # @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
57
+ #
58
+ module Option
59
+ def left_class
60
+ None
61
+ end
62
+
63
+ def right_class
64
+ Some
65
+ end
66
+
67
+ # @return [true, false] true if the option is `None`,
68
+ # false otherwise.
69
+ #
70
+ def empty?
71
+ is_a?(None)
72
+ end
73
+
74
+ # Returns the option's value if it is nonempty,
75
+ # or `nil` if it is empty. Useful for unwrapping
76
+ # option's value.
77
+ #
78
+ # @return [Object, nil] the option's value if it is
79
+ # nonempty or `nil` if it is empty
80
+ #
81
+ # @example
82
+ # Option(24).or_nil #=> 24
83
+ # Option(nil).or_nil #=> nil
84
+ #
85
+ def or_nil
86
+ get_or_else { nil }
87
+ end
88
+
89
+ module Mixin
90
+ # @param value [any]
91
+ # @return [Some, None]
92
+ def Option(value)
93
+ if value.nil?
94
+ None()
95
+ else
96
+ Some(value)
97
+ end
98
+ end
99
+
100
+ # @return [None]
101
+ def None
102
+ None.new
103
+ end
104
+
105
+ # @param value [any] except nil
106
+ # @return [None]
107
+ def Some(value)
108
+ Some.new(value)
109
+ end
110
+ end
111
+ end
112
+ end