deterministic 0.0.1 → 0.1.0

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