deterministic 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: 59f6546f0d7eb685094458bb948edd79835fcf7e
4
- data.tar.gz: 03dee18c4212bd61365220bd755cfd423671eb2e
3
+ metadata.gz: 925b4c6bf8684358c92969bd99bfaae834ecc96d
4
+ data.tar.gz: b9e69ef69dbd5fe6e050265d41a39375471b4acc
5
5
  SHA512:
6
- metadata.gz: d1f1061ada1b36192740b58b3cd38b37af0d822e96f1a4a4473ea8e3c85d66b2f05dedb1456491ab0f63085d5f07d4385934a16b25df948e84021ff3bdbd1d3c
7
- data.tar.gz: e5f095fa97070555482469da9a6175c35abacbed7a9ad106ab0d667945daa405931f8c73b8f862845415ecbc74dd7c000c86ae1eb42691791cc4050c8308c3d9
6
+ metadata.gz: e1d622aec6a69088627ab300883863815e85b7f67b4080072271644aaba31591942f2d23249751047f5d25e850a933414804a59641bb216a3e572a817aebde75
7
+ data.tar.gz: 9637ca25bc179cfc53464b618e3c8e09332bd3e52085a1c750aaf2715feea65c5ccd701be4cf5388f334f5835007b532ff5f87d8b8edd142592e59bf3656f9a0
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Guardfile ADDED
@@ -0,0 +1,15 @@
1
+ guard :bundler do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+
11
+ # Turnip features and steps
12
+ watch(%r{^spec/acceptance/(.+)\.feature$})
13
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
14
+ end
15
+
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
- # .
1
+ # Deterministic
2
2
 
3
- TODO: Write a gem description
3
+ This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadic)
4
4
 
5
5
  ## Installation
6
6
 
7
7
  Add this line to your application's Gemfile:
8
8
 
9
- gem '.'
9
+ gem 'deterministic'
10
10
 
11
11
  And then execute:
12
12
 
@@ -14,11 +14,77 @@ And then execute:
14
14
 
15
15
  Or install it yourself as:
16
16
 
17
- $ gem install .
17
+ $ gem install deterministic
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ ### Either#attempt_all
22
+ The basic idea is to execute a chain of units of work and make sure all return either `Success` or `Failure`.
23
+
24
+ ```ruby
25
+ Either.attempt_all do
26
+ try { 1 }
27
+ try { |prev| prev + 1 }
28
+ end # => Success(2)
29
+ ```
30
+ Take notice, that the result of of unit of work will be passed to the next one. So the result of prepare_somehing will be something in the second try.
31
+
32
+ If any of the units of work in between fail, the rest will not be executed and the last `Failure` will be returned.
33
+
34
+ ```ruby
35
+ Either.attempt_all do
36
+ try { 1 }
37
+ try { raise "error" }
38
+ try { 2 }
39
+ end # => Failure(RuntimeError("error"))
40
+ ```
41
+
42
+ However, the real fun starts if you use it with your own context. You can use this as a state container (meh!) or to pass a dependency locator:
43
+
44
+ ```ruby
45
+ class Context
46
+ attr_accessor :env, :settings
47
+ def some_service
48
+ end
49
+ end
50
+
51
+ # exemplary unit of work
52
+ module LoadSettings
53
+ def self.call(env)
54
+ settings = load(env)
55
+ settings.nil? ? Failure('could not load settings') : Success(settings)
56
+ end
57
+
58
+ def load(env)
59
+ end
60
+ end
61
+
62
+ Either.attempt_all(context) do
63
+ # this unit of work explicitly returns success or failure
64
+ # no exceptions are catched and if they occur, well, they behave as expected
65
+ # methods from the context can be accessed, the use of self for setters is necessary
66
+ let { self.settings = LoadSettings.call(env) }
67
+
68
+ # with #try all exceptions will be transformed into a Failure
69
+ try { do_something }
70
+ end
71
+ ```
72
+
73
+ ### Pattern matching
74
+ Now that you have some result, you want to control flow by providing patterns.
75
+
76
+ ```ruby
77
+ Success(1).match do
78
+ success { |v| "success #{v}"}
79
+ failure { |v| "failure #{v}"}
80
+ either { |v| "either #{v}"}
81
+ end # => "either 1"
82
+ ```
83
+ Note: the inner value has been unwrapped!
84
+
85
+ Note2: only the last matching pattern block will be executed
86
+
87
+ The result returned will be the result of the last `#try` or `#let`
22
88
 
23
89
  ## Contributing
24
90
 
@@ -27,3 +93,8 @@ TODO: Write usage instructions here
27
93
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
94
  4. Push to the branch (`git push origin my-new-feature`)
29
95
  5. Create new Pull Request
96
+
97
+ ## Inspirations
98
+ * My [Monadic gem](http://github.com/pzol/monadic) of course
99
+ * `#attempt_all` was somewhat inspired by [An error monad in Clojure](http://brehaut.net/blog/2011/error_monads)
100
+ * [Pithyless' rumblings](https://gist.github.com/pithyless/2216519)
@@ -20,4 +20,9 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-bundler"
26
+ spec.add_development_dependency "guard-rspec"
27
+ spec.add_development_dependency "simplecov"
23
28
  end
@@ -0,0 +1,42 @@
1
+ require 'ostruct'
2
+ module Deterministic::Either
3
+ def self.attempt_all(context=OpenStruct.new, &block)
4
+ AttemptAll.new(context, &block).call
5
+ end
6
+
7
+ class AttemptAll
8
+ class EitherExpectedError < StandardError; end
9
+ def initialize(context, &block)
10
+ @context = context || self
11
+ @tries = []
12
+ instance_eval(&block)
13
+ end
14
+
15
+ def call(initial=nil)
16
+ result = @tries.inject(Success(initial)) do |acc, try|
17
+ acc.success? ? acc.bind(try.call(acc)) : acc
18
+ end
19
+ end
20
+
21
+ def try(&block)
22
+ try_p = ->(acc) {
23
+ begin
24
+ value = @context.instance_exec(acc.value, &block)
25
+ Success(value)
26
+ rescue => ex
27
+ Failure(ex)
28
+ end
29
+ }
30
+
31
+ @tries << try_p
32
+ end
33
+
34
+ def let(sym=nil, &block)
35
+ @tries << ->(acc) {
36
+ @context.instance_exec(acc, &block).tap do |value|
37
+ raise EitherExpectedError unless value.is_a? Either
38
+ end
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Deterministic::Either
2
+ class Failure < Either; end
3
+ end
@@ -0,0 +1,44 @@
1
+ module Deterministic::Either
2
+ def match(proc=nil, &block)
3
+ match = Match.new(self)
4
+ match.instance_eval &(proc || block)
5
+ match.result
6
+ end
7
+
8
+ class Match
9
+ def initialize(either)
10
+ @either = either
11
+ @collection = []
12
+ end
13
+
14
+ def success(value=nil, &block)
15
+ q(:success, value, block)
16
+ end
17
+
18
+ def failure(value=nil, &block)
19
+ q(:failure, value, block)
20
+ end
21
+
22
+ def either(value=nil, &block)
23
+ q(:either, value, block)
24
+ end
25
+
26
+ def result
27
+ matcher = @collection.select { |m| m.first.call(@either.value) }.last
28
+ matcher.last.call(@either.value)
29
+ end
30
+
31
+ private
32
+ def q(type, condition, block)
33
+ if condition.nil?
34
+ condition_p = ->(v) { true }
35
+ elsif condition.is_a?(Proc)
36
+ condition_p = condition
37
+ else
38
+ condition_p = ->(v) { condition == @either.value }
39
+ end
40
+
41
+ @collection << [condition_p, block] if @either.is? type
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Deterministic::Either
2
+ class Success < Either; end
3
+ end
@@ -0,0 +1,58 @@
1
+ module Deterministic::Either
2
+ def Success(value)
3
+ Success.unit(value)
4
+ end
5
+
6
+ def Failure(value)
7
+ Failure.unit(value)
8
+ end
9
+
10
+ class Either
11
+ def self.unit(value)
12
+ return value if value.is_a? Either
13
+ # return Failure.new(value) if value.nil? || (value.respond_to?(:empty?) && value.empty?) || !value
14
+ # return Success.new(value)
15
+ return new(value)
16
+ end
17
+
18
+ def is?(s)
19
+ const_name = s.slice(0,1).capitalize + s.slice(1..-1)
20
+ is_a? Module.const_get(const_name)
21
+ end
22
+
23
+ def success?
24
+ is_a? Success
25
+ end
26
+
27
+ def failure?
28
+ is_a? Failure
29
+ end
30
+
31
+ def bind(other)
32
+ return self if failure?
33
+ return other if other.is_a? Either
34
+ # return concat(proc) if proc.is_a? Either
35
+
36
+ # begin
37
+ # Either(call(proc, block))
38
+ # rescue StandardError => error
39
+ # Failure(error)
40
+ # end
41
+ end
42
+
43
+ # get the underlying value
44
+ def value
45
+ @value
46
+ end
47
+
48
+ def ==(other)
49
+ return false unless other.is_a? self.class
50
+ @value == other.instance_variable_get(:@value)
51
+ end
52
+
53
+ private
54
+ def initialize(value)
55
+ @value = value
56
+ end
57
+ end
58
+ end
@@ -1,3 +1,3 @@
1
1
  module Deterministic
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/deterministic.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require "deterministic/version"
2
2
 
3
- module Deterministic
4
- # Your code goes here...
5
- end
3
+ module Deterministic; end
4
+
5
+ require 'deterministic/either'
6
+ require 'deterministic/either/match'
7
+ require 'deterministic/either/attempt_all'
8
+ require 'deterministic/either/success'
9
+ require 'deterministic/either/failure'
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'deterministic'
3
+
4
+ include Deterministic::Either
5
+ include Deterministic
6
+
7
+ describe Deterministic::Either::AttemptAll do
8
+ it "#try evaluates the result as Success" do
9
+ expect(
10
+ Either.attempt_all do
11
+ try { @a = 1 }
12
+ try { @b = @a + 1 }
13
+ end
14
+ ).to eq Success(2)
15
+ end
16
+
17
+ it "#try values are passed to the next command" do
18
+ expect(
19
+ Either.attempt_all do
20
+ try { 1 }
21
+ try { |v| v + 1 }
22
+ end
23
+ ).to eq Success(2)
24
+ end
25
+
26
+ it "try treat exceptions as Failure" do
27
+ attempt = Either.attempt_all do
28
+ try { 1 }
29
+ try { raise "error" }
30
+ end.value
31
+ expect(attempt).to be_a RuntimeError
32
+ expect(attempt.message).to eq "error"
33
+ end
34
+
35
+ it "don't continue on failure" do
36
+ fake = double()
37
+ expect(
38
+ Either.attempt_all do
39
+ try { 1 }
40
+ let { Failure(2) }
41
+ try { fake.should_not_be_called }
42
+ end
43
+ ).to eq Failure(2)
44
+ end
45
+
46
+ it "#let expects Success or Failure" do
47
+ expect(
48
+ Either.attempt_all do
49
+ let { Success(1) }
50
+ end
51
+ ).to eq Success(1)
52
+
53
+ expect {
54
+ Either.attempt_all do
55
+ let { 1 }
56
+ end
57
+ }.to raise_error(Deterministic::Either::AttemptAll::EitherExpectedError)
58
+ end
59
+
60
+ it "#let will not catch errors" do
61
+ expect {
62
+ Either.attempt_all do
63
+ let { raise "error" }
64
+ end
65
+ }.to raise_error
66
+ end
67
+
68
+ it "works with an OpenStruct" do
69
+ context = OpenStruct.new
70
+ attempt = Either.attempt_all(context) do
71
+ let { Success(self.alpha = 2) }
72
+ try { self.res = 1 }
73
+ end
74
+
75
+ expect(context.res).to eq 1
76
+ expect(context.alpha).to eq 2
77
+ end
78
+
79
+ it "can operate in the context of a host" do
80
+ class Host
81
+ attr_accessor :a
82
+ def initialize
83
+ @a = 1
84
+ end
85
+ end
86
+
87
+ host = Host.new
88
+
89
+ expect(
90
+ Either.attempt_all(host) do
91
+ try { self.a += 1 }
92
+ try { self.a + 1 }
93
+ end
94
+ ).to eq Success(3)
95
+
96
+ expect(host.a).to eq 2
97
+ end
98
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'deterministic'
3
+
4
+ include Deterministic::Either
5
+
6
+ describe Deterministic::Either::Match do
7
+ it "can match Success" do
8
+ expect(
9
+ Success(1).match do
10
+ success { |v| "Success #{v}" }
11
+ failure { |v| "Failure #{v}" }
12
+ end
13
+ ).to eq "Success 1"
14
+ end
15
+
16
+ it "can match Failure" do
17
+ expect(
18
+ Failure(1).match do
19
+ success { |v| "Success #{v}" }
20
+ failure { |v| "Failure #{v}" }
21
+ end
22
+ ).to eq "Failure 1"
23
+ end
24
+
25
+ it "can match with values" do
26
+ expect(
27
+ Failure(2).match do
28
+ success { |v| "not matched s" }
29
+ success(1) { |v| "not matched s1" }
30
+ failure(1) { |v| "not matched f1" }
31
+ failure(2) { |v| "matched #{v}" }
32
+ failure(3) { |v| "not matched f3" }
33
+ end
34
+ ).to eq "matched 2"
35
+ end
36
+
37
+ it "can match either" do
38
+ expect(
39
+ Failure(2).match do
40
+ success { |v| "not matched s" }
41
+ either(2) { |v| "either #{v}" }
42
+ failure(3) { |v| "not matched f3" }
43
+ end
44
+ ).to eq "either 2"
45
+ end
46
+
47
+ it "can mach with lambdas" do
48
+ expect(
49
+ Success(1).match do
50
+ success ->(v) { v == 1 } { |v| "matched #{v}" }
51
+ end
52
+ ).to eq "matched 1"
53
+ end
54
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'deterministic'
3
+
4
+ include Deterministic::Either
5
+
6
+ describe Deterministic::Either::Success do
7
+ subject { described_class.unit(1) }
8
+
9
+ specify { expect(subject).to be_an_instance_of described_class }
10
+ specify { expect(subject.value).to eq 1 }
11
+ specify { expect(subject).to be_success }
12
+ specify { expect(subject).not_to be_failure }
13
+
14
+ # public constructor #Success[]
15
+ specify { expect(described_class.unit(1)).to be_an_instance_of described_class }
16
+ specify { expect(subject).to eq(described_class.unit(1))}
17
+
18
+ it "handles chaining using &" do
19
+ expect(Success(1).bind Success(2)).to eq(Success(2))
20
+ expect(Success(1).bind Failure(2)).to eq(Failure(2))
21
+ end
22
+
23
+ specify { expect(Success(Success(1))).to eq Success(1) }
24
+ specify { expect(Success(1).is? :success).to be true }
25
+ specify { expect(Success(1).is? :either).to be true }
26
+ specify { expect(Success(1).is? :failure).to be false }
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ RSpec.configure do |config|
5
+ # Limit the spec run to only specs with the focus metadata. If no specs have
6
+ # the filtering metadata and `run_all_when_everything_filtered = true` then
7
+ # all specs will run.
8
+ config.filter_run :focus
9
+
10
+ # Run all specs when none match the provided filter. This works well in
11
+ # conjunction with `config.filter_run :focus`, as it will run the entire
12
+ # suite when no specs have `:filter` metadata.
13
+ config.run_all_when_everything_filtered = true
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.expect_with :rspec do |c|
20
+ c.syntax = :expect
21
+ end
22
+ config.order = 'random'
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deterministic
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
  - Piotr Zolnierek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-26 00:00:00.000000000 Z
11
+ date: 2013-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,76 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
41
111
  description: A gem providing failsafe flow
42
112
  email:
43
113
  - pz@anixe.pl
@@ -46,14 +116,25 @@ extensions: []
46
116
  extra_rdoc_files: []
47
117
  files:
48
118
  - .gitignore
119
+ - .rspec
49
120
  - Gemfile
121
+ - Guardfile
50
122
  - LICENSE.txt
51
123
  - README.md
52
124
  - Rakefile
53
125
  - deterministic.gemspec
54
126
  - either.rb
55
127
  - lib/deterministic.rb
128
+ - lib/deterministic/either.rb
129
+ - lib/deterministic/either/attempt_all.rb
130
+ - lib/deterministic/either/failure.rb
131
+ - lib/deterministic/either/match.rb
132
+ - lib/deterministic/either/success.rb
56
133
  - lib/deterministic/version.rb
134
+ - spec/lib/deterministic/attempt_all_spec.rb
135
+ - spec/lib/deterministic/either/match_spec.rb
136
+ - spec/lib/deterministic/either/success_spec.rb
137
+ - spec/spec_helper.rb
57
138
  homepage: http://github.com/pzol/deterministic
58
139
  licenses:
59
140
  - MIT
@@ -78,4 +159,8 @@ rubygems_version: 2.0.14
78
159
  signing_key:
79
160
  specification_version: 4
80
161
  summary: see above
81
- test_files: []
162
+ test_files:
163
+ - spec/lib/deterministic/attempt_all_spec.rb
164
+ - spec/lib/deterministic/either/match_spec.rb
165
+ - spec/lib/deterministic/either/success_spec.rb
166
+ - spec/spec_helper.rb