fear 0.0.1

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