kleisli-validation 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.
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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kleisli-validation.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Brian Zeligson
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,76 @@
1
+ # Kleisli::Validation
2
+
3
+ A Validation monad built on top of the nice [Kleisli](https://github.com/txus/kleisli)
4
+ gem.
5
+
6
+ Validation is like an Either, but it allows you to accumulate Failures of the
7
+ same Semigroup using the applicative functor's apply operator.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'kleisli-validation'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install kleisli-validation
24
+
25
+ ## Usage
26
+
27
+ Usage is practically identical to the [Either](https://github.com/txus/kleisli#either)
28
+ Monad provided by kleisli, substituting Success for Right (and .success for .right)
29
+ as well as Failure for Left (and .failure for .left).
30
+
31
+ The main difference is in the handling of errors when using the applicative
32
+ functor's apply, as seen here.
33
+
34
+ ```ruby
35
+ require "kleisli-validation"
36
+
37
+ add = -> x, y { x + y }
38
+ Success(add) * Success(10) * Success(2)
39
+ # => Success(12)
40
+ Success(add) * Failure("error") * Success(2)
41
+ # => Failure("error")
42
+
43
+ # new case supported by Validation
44
+ add3 = -> x, y, z { x + y + z }
45
+ Success(add3) * Failure(["error"]) * Success(2) * Failure(["another error"])
46
+ # => Failure(["error", "another error"])
47
+
48
+ Success(add3) * Failure(x: "No good") * Success(2) * Failure(x: "Still no good", y: "Also no good")
49
+ # => Failure(x: ["No good", "Still no good"], y: "Also no good")
50
+ ```
51
+
52
+
53
+ ###Semigroups
54
+
55
+ The values inside your Failures must all belong to the same Semigroup in
56
+ order to accumulate errors using the apply operator.
57
+
58
+ A Semigroup is any class that implements the sappend method as follows:
59
+
60
+ ```haskell
61
+ sappend :: a -> a -> a
62
+ ```
63
+
64
+ This gem provides a Semigroup monkeypatch for Array and Hash, see
65
+ semigroup\_instances.rb for examples and implementation.
66
+
67
+ Using Failure values with no sappend implementation or Failure values of
68
+ differing types in one application will yield an ArgumentError.
69
+
70
+ ## Contributing
71
+
72
+ 1. Fork it ( https://github.com/[my-github-username]/kleisli-validation/fork )
73
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
74
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
75
+ 4. Push to the branch (`git push origin my-new-feature`)
76
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kleisli/validation'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kleisli-validation"
8
+ spec.version = Kleisli::Validation::VERSION
9
+ spec.authors = ["Brian Zeligson"]
10
+ spec.email = ["brian.zeligson@gmail.com"]
11
+ spec.summary = %q{Validation Monad for Kleisli gem}
12
+ spec.description = %q{Validation is an Either which can accumulate
13
+ failures when used with the apply operator (*)
14
+ and a Semigroup on the Left.}
15
+ spec.homepage = ""
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.5"
26
+ spec.add_dependency "kleisli", "~> 0.2.6"
27
+ end
@@ -0,0 +1,21 @@
1
+ require "kleisli"
2
+
3
+ # monkey patch some default semigroup instances
4
+
5
+ class Array
6
+
7
+ def sappend(other)
8
+ concat(other)
9
+ end
10
+ end
11
+
12
+ class Hash
13
+
14
+ def sappend(other)
15
+ merge(other) do |k, va, vb|
16
+ va = [va] unless va.kind_of?(Array)
17
+ vb = [vb] unless vb.kind_of?(Array)
18
+ va.concat(vb)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module Kleisli
2
+ class Validation
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,112 @@
1
+ #require "kleisli/validation/version"
2
+ require "kleisli"
3
+ require "kleisli/semigroup_instances.rb"
4
+
5
+ module Kleisli
6
+ class Validation < Either
7
+ VERSION = "0.0.1"
8
+ attr_accessor :success, :failure
9
+
10
+ def *(other)
11
+ self >-> f {
12
+ other >-> val {
13
+ Success(f.arity > 1 ? f.curry.call(val) : f.call(val))
14
+ }
15
+ }
16
+ end
17
+
18
+ class Success < Validation
19
+ alias value success
20
+
21
+ def *(other)
22
+ if other.class == Success
23
+ super
24
+ else
25
+ other
26
+ end
27
+ end
28
+
29
+ def initialize(success)
30
+ @success = success
31
+ end
32
+
33
+ def >(f)
34
+ f.call(@success)
35
+ end
36
+
37
+ def fmap(&f)
38
+ Success.new(f.call(@success))
39
+ end
40
+
41
+ def to_maybe
42
+ Maybe::Some.new(@success)
43
+ end
44
+
45
+ def or(other=nil, &other_blk)
46
+ self
47
+ end
48
+
49
+ def to_s
50
+ "Success(#{@success.inspect})"
51
+ end
52
+ alias inspect to_s
53
+ end
54
+
55
+ class Failure < Validation
56
+ alias value failure
57
+
58
+ def *(other)
59
+ if other.class == Failure
60
+ unless self.failure.class == other.failure.class &&
61
+ self.failure.respond_to?(:sappend)
62
+ raise ArgumentError,
63
+ "Failures must contain members of a common Semigroup"
64
+ end
65
+ Failure(self.failure.sappend(other.failure))
66
+ else
67
+ self
68
+ end
69
+ end
70
+
71
+ def initialize(failure)
72
+ @failure = failure
73
+ end
74
+
75
+ def >(f)
76
+ self
77
+ end
78
+
79
+ def fmap(&f)
80
+ self
81
+ end
82
+
83
+ def to_maybe
84
+ Maybe::None.new
85
+ end
86
+
87
+ def or(other=self, &other_blk)
88
+ if other_blk
89
+ other_blk.call(@failure)
90
+ else
91
+ other
92
+ end
93
+ end
94
+
95
+ def to_s
96
+ "Failure(#{@failure.inspect})"
97
+ end
98
+ alias inspect to_s
99
+ end
100
+ end
101
+ end
102
+
103
+ Success = Kleisli::Validation::Success.method(:new)
104
+ Failure = Kleisli::Validation::Failure.method(:new)
105
+
106
+ def Success(v)
107
+ Kleisli::Validation::Success.new(v)
108
+ end
109
+
110
+ def Failure(v)
111
+ Kleisli::Validation::Failure.new(v)
112
+ end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+
3
+ class ValidationTest < Minitest::Test
4
+ def test_lift_right
5
+ assert_equal 3, Success(3).value
6
+ end
7
+
8
+ def test_lift_left
9
+ assert_equal "error", Failure("error").value
10
+ end
11
+
12
+ def test_bind_right
13
+ v = Success(1) >-> x {
14
+ if x == 1
15
+ Success(x + 90)
16
+ else
17
+ Failure("FAIL")
18
+ end
19
+ }
20
+ assert_equal Success(91), v
21
+ end
22
+
23
+ def test_bind_left
24
+ v = Failure("error") >-> x {
25
+ Success(x * 20)
26
+ }
27
+ assert_equal Failure("error"), v
28
+ end
29
+
30
+ def test_fmap_right
31
+ assert_equal Success(2), Success(1).fmap { |x| x * 2 }
32
+ end
33
+
34
+ def test_fmap_left
35
+ assert_equal Failure("error"), Failure("error").fmap { |x| x * 2 }
36
+ end
37
+
38
+ def test_to_maybe_right
39
+ assert_equal Some(2), Success(1).fmap { |x| x * 2 }.to_maybe
40
+ end
41
+
42
+ def test_to_maybe_left
43
+ assert_equal None(), Failure("error").fmap { |x| x * 2 }.to_maybe
44
+ end
45
+
46
+ def test_pointfree
47
+ assert_equal Success(10), Success(5) >> F . fn(&Success) . *(2)
48
+ end
49
+
50
+ def test_applicative_functor_right_arity_1
51
+ assert_equal Success(20), Success(-> x { x * 2 }) * Success(10)
52
+ end
53
+
54
+ def test_applicative_functor_right_arity_2
55
+ assert_equal Success(20), Success(-> x, y { x * y }) * Success(10) * Success(2)
56
+ end
57
+
58
+ def test_applicative_functor_left
59
+ assert_equal Failure("error"), Success(-> x, y { x * y }) * Failure("error") * Success(2)
60
+ end
61
+
62
+ def test_accumulates_failures
63
+ assert_raises(ArgumentError) { Some(-> x, y { x+y }) * Failure(1) * Failure([2]) }
64
+
65
+ assert_equal Failure(["error", "another error"]),
66
+ (Success(-> x, y, z { x * y * z }) * Failure(["error"]) *
67
+ Success(2) * Failure(["another error"]))
68
+
69
+ assert_equal Failure(a: 1, b: [2, 3]),
70
+ (Success(-> x, y, z { x * y * z }) * Failure(a: 1, b: 2) *
71
+ Success(2) * Failure(b: 3))
72
+ end
73
+ end
@@ -0,0 +1,4 @@
1
+ require 'kleisli'
2
+ require 'kleisli/validation'
3
+ require 'minitest'
4
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kleisli-validation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Zeligson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.7'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '10.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '10.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '5.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '5.5'
62
+ - !ruby/object:Gem::Dependency
63
+ name: kleisli
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.2.6
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.2.6
78
+ description: ! "Validation is an Either which can accumulate\n failures
79
+ when used with the apply operator (*)\n and a Semigroup
80
+ on the Left."
81
+ email:
82
+ - brian.zeligson@gmail.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - LICENSE.txt
90
+ - README.md
91
+ - Rakefile
92
+ - kleisli-validation.gemspec
93
+ - lib/kleisli/semigroup_instances.rb
94
+ - lib/kleisli/validation.rb
95
+ - lib/kleisli/validation/version.rb
96
+ - test/kleisli/validation_test.rb
97
+ - test/test_helper.rb
98
+ homepage: ''
99
+ licenses:
100
+ - MIT
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 1.8.24
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: Validation Monad for Kleisli gem
123
+ test_files:
124
+ - test/kleisli/validation_test.rb
125
+ - test/test_helper.rb
126
+ has_rdoc: