yopt 0.2.0 → 0.3.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: b2b1eaa4ca004e729d902f06b93fca46dd526175
4
- data.tar.gz: 920c701a92b2f4a74698b376e81035d049d9bc74
3
+ metadata.gz: c229455d1c89285665fc049eba0ba28e3ccb476e
4
+ data.tar.gz: ce0b59c5d06a0860ab05e23b5ec66e0fd98fae8c
5
5
  SHA512:
6
- metadata.gz: 1fe9eed7ba9150bef20625b851109cfa872ed1b961ef3e9552126c200f841e45faf978b6d9f2539e516b236ef9a42ae224ccd260363430f75be15a63ae2f3d73
7
- data.tar.gz: caff9b6961f3217f9d1894bda7bf30227026d86ff28fe043a20da52c05955c9837b93b9ce058a70d1e73583ba5b0294b9e18e1773190c898b73d49d7795f624d
6
+ metadata.gz: 08b12da1909c7357a303e6d1f467e439561d02a5dc81d34a014d02af574d3b441c64c3be52fc5babe208a85c27ea20ef47fe83555bdb7435453e7ec497aaf084
7
+ data.tar.gz: a4935ac26dbf235085138e6adcbad5d6a3f172f744d239c53c6988d7f2606c7e3853dca3581dcbc9e5b0d9eb4180f3ef3e6f9e9087a01b1a0bf201e4f70dfdce
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.org/lbarasti/yopt.svg?branch=master)](https://travis-ci.org/lbarasti/yopt) [![Coverage Status](https://coveralls.io/repos/github/lbarasti/yopt/badge.svg?branch=master)](https://coveralls.io/github/lbarasti/yopt?branch=master)
2
+
1
3
  # Yopt
2
4
 
3
5
  A [Scala](http://www.scala-lang.org/api/current/index.html#scala.Option) inspired gem that introduces `Option`s to Ruby while aiming for an idiomatic API.
@@ -157,11 +159,24 @@ Option[42].zip Option[0], Option["str"] # Some([42, 0, "str"])
157
159
  ```
158
160
 
159
161
 
162
+ ### #or_else
163
+ When writing an application whose functionality depends on the availability of other services/information, it's a good idea to prepare for the scenario where such services/information are not available.
164
+
165
+ `#or_else` lets us define a chain of block calls that will stop as soon as one of them returns a non-empty option.
166
+ ```ruby
167
+ get_postcode = -> email do
168
+ get_from_cache[email]
169
+ .or_else { get_from_db[email] }
170
+ .or_else { get_from_remote[email] }
171
+ end
172
+ ```
173
+
174
+
160
175
  ### #grep
161
176
  We often find ourselves filtering data before applying a transformation...
162
177
 
163
178
  ```ruby
164
- opt.filter {|v| (1...10).include? v}.map {|v| v + 1}
179
+ opt.select {|v| (1...10).include? v}.map {|v| v + 1}
165
180
  ```
166
181
 
167
182
  In this scenario, `Option#grep` can sometimes make the code more concise
@@ -177,7 +192,7 @@ is_positive = lambda {|x| x > 0}
177
192
 
178
193
  opt.grep(is_positive) {|v| Math.log(v)}
179
194
  # is equivalent to
180
- opt.filter(&is_positive).map {|v| Math.log(v)}
195
+ opt.select(&is_positive).map {|v| Math.log(v)}
181
196
  ```
182
197
 
183
198
 
@@ -8,7 +8,7 @@ module Yopt
8
8
  module Option
9
9
  include Enumerable
10
10
  def self.ary_to_type value
11
- raise Option.invalid_argument('an array-like object', value) unless value.respond_to? :to_ary
11
+ raise Option.invalid_argument('Argument must be an array-like object', value) unless value.respond_to? :to_ary
12
12
  return value if value.is_a? Option
13
13
  if value.to_ary.empty? then None else Some.new(value.to_ary.first) end
14
14
  end
@@ -43,9 +43,10 @@ module Yopt
43
43
  def ^ lambda
44
44
  self | Yopt.lift(&lambda)
45
45
  end
46
- def or_else other
47
- raise Option.invalid_argument('an Option', other) unless other.is_a? Option
48
- if empty? then other else self end
46
+ def or_else
47
+ return self unless empty?
48
+ other = yield
49
+ other.is_a?(Option) ? other : raise(Option.invalid_argument('Block should evaluate to an Option', other))
49
50
  end
50
51
  def get_or_else
51
52
  raise ArgumentError, 'missing block' unless block_given?
@@ -58,7 +59,7 @@ module Yopt
58
59
  def inspect() to_s end
59
60
  private
60
61
  def self.invalid_argument type_str, arg
61
- TypeError.new "Argument must be #{type_str}. Found #{arg.class}"
62
+ TypeError.new "#{type_str}. Found #{arg.class}"
62
63
  end
63
64
  end
64
65
  class Some
@@ -1,3 +1,3 @@
1
1
  module Yopt
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yopt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - lorenzo.barasti
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-02 00:00:00.000000000 Z
11
+ date: 2016-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,34 +52,32 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
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'
55
69
  description: The Option type models the possible absence of a value. It lets us deal
56
70
  with the uncertainty related to such a value being there without having to resort
57
71
  to errors or conditional blocks.
58
- email:
72
+ email: yopt-user-group@googlegroups.com
59
73
  executables: []
60
74
  extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
63
- - ".gitignore"
64
- - ".travis.yml"
65
- - CODE_OF_CONDUCT.md
66
- - Gemfile
67
77
  - LICENSE.txt
68
78
  - README.md
69
- - Rakefile
70
- - bin/console
71
- - bin/setup
72
- - docs/README.template.md
73
- - docs/basics.rb
74
- - docs/enumerable_methods.md
75
- - docs/full_example_snippet.rb
76
- - docs/including yopt.md
77
- - docs/usage_reduce.rb
78
- - docs/usage_snippet.rb
79
- - docs/usage_zip.rb
80
79
  - lib/yopt.rb
81
80
  - lib/yopt/version.rb
82
- - yopt.gemspec
83
81
  homepage: http://lbarasti.github.io/yopt
84
82
  licenses:
85
83
  - MIT
@@ -105,4 +103,3 @@ signing_key:
105
103
  specification_version: 4
106
104
  summary: Scala-inspired Options for the idiomatic Rubyist
107
105
  test_files: []
108
- has_rdoc:
data/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /vendor
11
- /yopt-0.1.0.gem
12
- /_site/
@@ -1,5 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0.0
4
- - 2.2.0
5
- before_install: gem install bundler -v 1.10.6
@@ -1,13 +0,0 @@
1
- # Contributor Code of Conduct
2
-
3
- As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
-
5
- We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
-
7
- Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
-
9
- Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
-
11
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
-
13
- This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'pry', '~> 0.10.3'
4
- gem 'rb-readline', '~> 0.5.3'
5
- # Specify your gem's dependencies in yopt.gemspec
6
- gemspec
data/Rakefile DELETED
@@ -1,39 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
3
-
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
8
- end
9
-
10
- task :docs do
11
- partial_keyword = '<<<<<'
12
- ignore_keyword = "# IGNORE\n"
13
- comment_keyword = "# COMMENT\n"
14
- src = './docs/README.template.md'
15
- target = './README.md'
16
- content = File.readlines(src).flat_map {|line|
17
- if line.lstrip.start_with?(partial_keyword)
18
- partial_file = line.lstrip[partial_keyword.size...-1]
19
- sh 'ruby', partial_file
20
- File.readlines partial_file
21
- else
22
- line
23
- end
24
- }
25
- File.open(target, 'w') {|f|
26
- content.reject{|line|
27
- line.end_with?(ignore_keyword)
28
- }.each_cons(2){|line1,line2|
29
- next if line1.end_with?(comment_keyword)
30
- if line2.end_with?(comment_keyword)
31
- f.puts "#{line1.chomp} # #{line2.match(/'(.*)'/)[1]}\n"
32
- else
33
- f.puts line1
34
- end
35
- }
36
- }
37
- end
38
-
39
- task :default => :test
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "yopt"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- require "pry"
10
- Pry.start
11
-
data/bin/setup DELETED
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here
@@ -1,215 +0,0 @@
1
- # Yopt
2
-
3
- A [Scala](http://www.scala-lang.org/api/current/index.html#scala.Option) inspired gem that introduces `Option`s to Ruby while aiming for an idiomatic API.
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'yopt'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install yopt
20
-
21
- ## Basic usage
22
-
23
- The Option type models the possible absence of a value. It lets us deal with the uncertainty related to such a value being there without having to resort to errors or conditional blocks.
24
-
25
- Instances of Option are either an instance of `Yopt::Some` - meaning the option contains a value - or the object `Yopt::None` - meaning the option is *empty*.
26
-
27
- ```ruby
28
- require 'yopt'
29
-
30
- some = Yopt::Some.new(42)
31
- none = Yopt::None
32
- ```
33
-
34
- We can access and manipulate the optional value by passing a block to `Option#map`.
35
-
36
- ```ruby
37
- some.map {|value| value + 2} # returns Some(44)
38
- none.map {|value| value + 2} # returns None
39
- ```
40
-
41
- When we are not interested in the result of a computation on the optional value, it is a good practice to use `Option#each` rather than `Option#map`. That will make our intention clearer.
42
-
43
- ```ruby
44
- some.each {|value| puts value} # prints 42
45
- none.each {|value| puts value} # does not print anything
46
- ```
47
-
48
- We can safely retrieve the optional value by passing a default value to `Option#get_or_else`
49
-
50
- ```ruby
51
- some.get_or_else {0} # returns 42
52
- none.get_or_else {0} # returns 0
53
- ```
54
-
55
- Notice how we are passing a block rather than an argument. This makes the evaluation of the default value lazy. In other words the block will only be evaluated if the caller in None.
56
-
57
- This gives us the possibility to react in a special way to a None value without breaking the API fluency, e.g.
58
-
59
- ```ruby
60
- opt.each {|v| do_something_with(v)}.get_or_else {log_failure}
61
- ```
62
-
63
- We can also filter the optional value depending on how it evaluates against a block via `Option#select`
64
-
65
- ```ruby
66
- some.select {|value| value < 0} # returns None
67
- none.select {|value| value < 0} # returns None
68
- some.select {|value| value > 0} # returns Some(42)
69
- ```
70
-
71
- We can easily turn any object into an Option by means of `Option.call` - aliased to `Option[]` for convenience.
72
- For instance, this is useful when dealing with functions that might return `nil` to express the absence of a result.
73
-
74
- ```ruby
75
- Yopt::Option[nil] # returns None
76
- Yopt::Option[42] # returns Some(42)
77
- ```
78
-
79
-
80
- A combination of the few methods just introduced already allows us to implement some pretty interesting logic. Checkout `basics.rb` in the docs folder to get some inspiration.
81
-
82
- ## Why opt?
83
-
84
- Using `Option`s reduces the amount of branching in our code and lets us deal with exceptional cases in a seamless way. No more check-for-nil, no more `rescue` blocks, just plain and simple data transformation.
85
-
86
- It also makes our code safer by treating *the absence of something* like a fully fledged object, and enables us to use the Null Object Pattern everywhere we want without the overhead of having to write specialized Null-type classes for different classes.
87
-
88
- ## Advanced Usage
89
- ### #reduce
90
- Given an Option `opt`, a value `c` and a lambda `f`,
91
- ```
92
- opt.reduce(c, &f)
93
- ```
94
- returns `c` if `opt` is `None`, and `f.(c, opt)` otherwise.
95
-
96
- This is a shortcut to
97
- ```
98
- opt.map{|v| f.(c,v)}.get_or_else {c}`
99
- ```
100
-
101
-
102
- ### #flatten and #flat_map
103
- When working with functions returning `Option`, we might end up dealing with nested options...
104
- ```ruby
105
- maybe_sqrt = lambda {|x| Yopt::Option[x >= 0 ? Math.sqrt(x) : nil]}
106
- maybe_increment = lambda {|x| Yopt::Option[x > 1 ? x + 1 : nil]}
107
-
108
- maybe_sqrt.(4).map {|v| maybe_increment.(v)} # Some(Some(3.0))
109
- maybe_sqrt.(1).map {|v| maybe_increment.(v)} # Some(None)
110
- ```
111
-
112
- Usually, this is not what we want, so we call `Option#flatten` on the result
113
- ```ruby
114
- maybe_sqrt.(4).map {|v| maybe_increment.(v)}.flatten # Some(3.0)
115
- maybe_sqrt.(1).map {|v| maybe_increment.(v)}.flatten # None
116
- ```
117
-
118
- `Option#flat_map` combines the two calls into one
119
-
120
- ```ruby
121
- maybe_sqrt.(4).flat_map {|v| maybe_increment.(v)} # Some(3.0)
122
- maybe_sqrt.(1).flat_map {|v| maybe_increment.(v)} # None
123
- ```
124
-
125
- A difference to keep in mind is that `#flatten` will raise an error if the wrapped value does not respond to `#to_ary`
126
- ```ruby
127
- Yopt.Option[42].flatten # raises TypeError: Argument must be an array-like object. Found Fixnum
128
- ```
129
- whereas #flat_map behaves like #map when the passed block does not return an array-like value
130
- ```ruby
131
- Yopt.Option[42].flat_map{|v| v} # returns Some(42)
132
- ```
133
-
134
-
135
- ### #zip
136
- When dealing with a set of `Option` instances, we might want to ensure that they are all defined - i.e. not __empty__ - before continuing a computation...
137
- ```ruby
138
- email_opt.each(&send_pass_recovery) unless (email_opt.empty? or captcha_opt.empty?)
139
- ```
140
-
141
- We can avoid `empty?` checks by using `Option#zip`
142
- ```ruby
143
- email_opt.zip(captcha_opt).each{|(email,_)| send_pass_recovery(email)}
144
- ```
145
-
146
- `Option#zip` returns `None` if any of the arguments is `None` or if the caller is `None`
147
- ```ruby
148
- Yopt::None.zip Option[42] # None
149
- Option[42].zip Yopt::None # None
150
- Option[42].zip Option[0], Yopt::None, Option[-1] # None
151
- ```
152
-
153
- When both the caller and all the arguments are defined then `zip` collects all the values in an Array wrapped in a `Yopt::Some`
154
-
155
- ```ruby
156
- Option[42].zip Option[0], Option["str"] # Some([42, 0, "str"])
157
- ```
158
-
159
-
160
- ### #grep
161
- We often find ourselves filtering data before applying a transformation...
162
-
163
- ```ruby
164
- opt.filter {|v| (1...10).include? v}.map {|v| v + 1}
165
- ```
166
-
167
- In this scenario, `Option#grep` can sometimes make the code more concise
168
-
169
- ```ruby
170
- opt.grep(1...10) {|v| v + 1}
171
- ```
172
-
173
- `Option#grep` supports lambdas as well
174
-
175
- ```ruby
176
- is_positive = lambda {|x| x > 0}
177
-
178
- opt.grep(is_positive) {|v| Math.log(v)}
179
- # is equivalent to
180
- opt.filter(&is_positive).map {|v| Math.log(v)}
181
- ```
182
-
183
-
184
- ## Haskell Data.Maybe cheat sheet
185
-
186
- Some (None?) might enjoy a comparison with Haskell's [Maybe](https://hackage.haskell.org/package/base/docs/Data-Maybe.html). Here is how the Data.Maybe API translate to Yopt.
187
- ```ruby
188
- maybe default f opt -> opt.map(&f).get_or_else {default}
189
- isJust opt -> not opt.empty?
190
- isNothing opt -> opt.empty?
191
- fromJust opt -> opt.get
192
- fromMaybe default opt -> opt.get_or_else {default}
193
- listToMaybe list -> Option.ary_to_type list
194
- maybeToList opt -> opt.to_a
195
- catMaybes listOfOptions -> listOfOptions.flatten
196
- mapMaybe f list -> list.flat_map &f
197
- ```
198
-
199
-
200
- ## Development
201
-
202
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
203
-
204
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
205
-
206
-
207
- ## Contributing
208
-
209
- Bug reports and pull requests are welcome on GitHub at https://github.com/lbarasti/yopt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
210
-
211
-
212
- ## License
213
-
214
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
215
-
@@ -1,37 +0,0 @@
1
- require_relative '../test/test_helper' # IGNORE
2
- # IGNORE
3
- require 'test/unit' # IGNORE
4
- include Test::Unit::Assertions # IGNORE
5
- # IGNORE
6
- class Cache < Hash
7
- def maybe_get key
8
- Yopt::Option[self[key]]
9
- end
10
-
11
- def store_if_some key, opt_value
12
- opt_value.each {|value| self.store(key, value)}
13
- end
14
- end
15
-
16
- def maybe_sqrt value
17
- opt = Yopt::Option[value]
18
- opt.select {|value| value >= 0}
19
- .map {|value| Math.sqrt(value)}
20
- end
21
-
22
- def get_info cache, key
23
- cache.maybe_get(key)
24
- .map {|value| "found value %.2f for key %s" % [value, key]}
25
- .get_or_else "value not found for key #{key}"
26
- end
27
-
28
- cache = Cache.new
29
-
30
- cache.store_if_some 42, maybe_sqrt(42)
31
-
32
- key = 42 + rand(2) # could be either 42 or 43
33
-
34
- puts get_info(cache, key)
35
- # IGNORE
36
- same_string(cache.maybe_get(key)).(Yopt::Some.new(Math.sqrt(42)).to_s) if key == 42 # IGNORE
37
- same_string(cache.maybe_get(key)).(Yopt::None.to_s) if key != 42 # IGNORE
@@ -1,21 +0,0 @@
1
- ## About Enumerable methods
2
- By including `Enumerable`, `Option` gives access to a set of methods which are either redundant or not really meaningful for a `Some` or a `None` object
3
-
4
- ```ruby
5
- :slice_after :slice_before :slice_when :take :take_while :drop :drop_while :chunk :sort_by :each_entry :each_slice :group_by :minmax_by :sort :each_with_index :partition :find_all :max_by :min_by :reverse_each :each_cons :min :max :minmax
6
- ```
7
-
8
- On the other hand, all the methods returning a boolean work as expected
9
- ```ruby
10
- :all? :any? :include? :none? :one? :member?
11
- ```
12
-
13
- The following methods work in a predictable way out-of-the-box
14
- ```ruby
15
- :reduce :inject :each_with_object :cycle :to_set :find_index :find :detect :first :count :to_a :entries :to_h
16
- ```
17
-
18
- While the following methods have custom definition to always return an `Option` rather than an `Array`
19
- ```ruby
20
- :collect :map :flat_map :collect_concat :reject :select :zip :grep
21
- ```
@@ -1,51 +0,0 @@
1
- require_relative '../test/test_helper' # IGNORE
2
- # IGNORE
3
- require 'test/unit' # IGNORE
4
- include Test::Unit::Assertions # IGNORE
5
-
6
- name2phone = [[:a, "+1 310-000-001"],
7
- [:b, "+1 323-000-002"],
8
- [:d, "+1 310-000-003"],
9
- [:e, "+1 213-000-004"],
10
- [:f, "+1 323-000-005"],
11
- [:h, "+1 213-000-006"],
12
- [:k, "+1 213-000-007"],
13
- [:s, "+1 310-000-009"],
14
- [:w, "+1 800-000-010"]]
15
-
16
- phone2postcode = [["+1 310-000-001", "CA 90210"],
17
- ["+1 310-000-003", "CA 90210"],
18
- ["+1 323-000-005", "CA 90028"],
19
- ["+1 213-000-006", "CA 90027"],
20
- ["+1 213-000-007", "CA 90027"],
21
- ["+1 800-000-010", "CA 91608"]]
22
-
23
- postcode2income = [["CA 90210", "$80000"],
24
- ["CA 91608", "$65000"]]
25
-
26
-
27
- lookup = -> (table, key) {
28
- row_or_nil = table.find { |row| row.first == key }
29
- Yopt::Option[row_or_nil] | :last
30
- }
31
-
32
- same_string( # IGNORE
33
- lookup.(name2phone, :s)
34
- ).('Some(+1 310-000-009)') # COMMENT
35
-
36
- same_string( # IGNORE
37
- lookup.(name2phone, :x)
38
- ).('None') # COMMENT
39
-
40
- same_string( # IGNORE
41
- same_string( # IGNORE
42
- same_string( # IGNORE
43
- lookup.(name2phone, :a)
44
- ).('Some(+1 310-000-001)') # COMMENT
45
- .flat_map {|phone| lookup.(phone2postcode, phone)}
46
- ).('Some(CA 90210)') # COMMENT
47
- .flat_map {|postcode| lookup.(postcode2income, postcode)}
48
- ).('Some($80000)') # COMMENT
49
-
50
- # or if you want to go crazy-functional
51
- lookup.(name2phone, :a) | lookup.curry[phone2postcode] | lookup.curry[postcode2income]
@@ -1,8 +0,0 @@
1
- Some might find that having to specify the scope every time we want to use `Some` or `None` a bit too verbose. If that is the case, please consider that module scoping is a good receipe to avoid hideous naming clashes.
2
-
3
- If you understand the risk then you can either `include Yopt` or cherry-pick the tools you need from the module like so:
4
-
5
- ```ruby
6
- Some = Yopt::Some
7
- None = Yopt::None
8
- ```
@@ -1,12 +0,0 @@
1
- require 'yopt'
2
-
3
- compute_increase = Yopt.lift {|user| 10000 if ['Bob', 'Joe', 'Eve'].member?(user)}
4
-
5
- base_salary = 20000
6
- user = ['Noel', 'Eve'].sample
7
- salary_increase_opt = compute_increase.(user) # None | Some(10000)
8
-
9
- salary_increase_opt.reduce(base_salary, &:+) # 20000 | 30000
10
- # is equivalent to
11
- salary_increase_opt.map{|increase| base_salary + increase} # None | Some(30000)
12
- .get_or_else(base_salary) # 20000 | 30000
@@ -1,77 +0,0 @@
1
- require_relative '../test/test_helper' # IGNORE
2
- # IGNORE
3
- require 'test/unit' # IGNORE
4
- include Test::Unit::Assertions # IGNORE
5
-
6
- # You can create an option from any object `obj`
7
- # In general, any object gets wrapped into a `Some` instance
8
- some = Yopt::Option[42] # Some(42)
9
- # The only exception being `nil`, which turns into `None`
10
- none = Yopt::Option[nil] # None
11
-
12
- # since Some and None have the same API, let's choose one of the two
13
- # randomly, and record the intermediate result of each computation
14
- # for both None and Some(42) following the convention <result_if_none> | <result_if_some>
15
- opt = [none, some].sample
16
-
17
- opt # None | Some(42)
18
- .select{|x| x > 0} # None | Some(42)
19
- .map(&:succ) # None | Some(43)
20
- .get_or_else(1) # 1 | 43
21
-
22
- # Calling select/reject on a Some instance can return None
23
- # Whereas a None can never be transformed into a Some
24
- # None | Some(42)
25
- opt.select{ |x| x < 0 } # None | None
26
-
27
- # One of the perks of working with options is that it allow us to stay
28
- # away from `nil` and `if` statements. You can still revert to using them if you need to though
29
- # There always is a better way to go though.
30
- opt.or_nil # nil | 42
31
- # Even better, if the option is None we can return a default value straight away
32
- opt.get_or_else 31 # 31 | 42
33
-
34
- # If we intend to stay in the Option domain for some more time we can swap None with an other Option
35
- opt.or_else(Yopt::Option[61]) # Some(61) | Some(42)
36
- .reject(&:even?) # Some(61) | None
37
- .get_or_else 0 # 61 | 0
38
-
39
- # Once in the Option domain, it's nice to avoid accessing
40
- # the content of an option explicitly as far as possible.
41
- # You might eventually need to extract the value wrapped by
42
- # Option.
43
- begin # None | Some(42)
44
- opt.get # raises a RuntimeError | 42
45
- rescue RuntimeError
46
- puts "cannot call #get on #{opt}"
47
- end
48
-
49
- # This does not look safe. Luckily we can check if the option
50
- # is None by calling `empty?` on it
51
- opt.empty? # true | false
52
-
53
-
54
- # v = s2.reduce(2){|default, opt_val| opt_val - default} # 47
55
- # n = Yopt::Some.new(v) # Some(47)
56
- # .collect{|x| if x % 2 == 1 then x + 1 end} # None
57
- # .map{|x| x ** 2} # None
58
-
59
- # if s1.reduce(s2.get, &:-) < 0 && s1.include?(42) # true
60
- # s2.collect{ n.empty? && n } # Some(None)
61
- # .flatten # None
62
- # .or_nil # nil
63
- # end
64
- # head_opt = Util.lift &:first
65
- # old_school_validate = -> email {if email.end_with?('my-domain.com') then email else nil end}
66
- # valid = 'user@my-domain.com'
67
- # invalid = '@gmail.com'
68
- # old_school_validate.(invalid).must_equal nil
69
- # old_school_validate.(valid).wont_be_nil
70
- # old_school_validate.(valid).must_equal valid
71
-
72
- # brand_new_validate = Util.lift &old_school_validate
73
- # brand_new_validate.(invalid).must_equal None
74
- # brand_new_validate.(valid).must_equal Some.new(valid)
75
-
76
- # user = brand_new_validate.(valid) | Util.lift{|email| email.split('@')[0]} | :upcase
77
- # user.get.must_equal "USER"
@@ -1,30 +0,0 @@
1
- require_relative '../test/test_helper' # IGNORE
2
- # IGNORE
3
- require 'test/unit' # IGNORE
4
- include Test::Unit::Assertions # IGNORE
5
-
6
- validate_email = Yopt.lift {|x| x if x.end_with? "@domain.com"}
7
- validate_password = Yopt.lift {|x| x if x.size > 8}
8
- hash_f = lambda {|str| str.each_char.map(&:ord).reduce(:+)}
9
-
10
- # returns Yopt::Some(Fixnum) if email and password are both valid
11
- # returns Yopt::None otherwise
12
- hash_credentials = -> (email, password) do
13
- maybe_email = validate_email.(email)
14
- maybe_password = validate_password.(password)
15
-
16
- maybe_email.zip(maybe_password)
17
- .map {|(valid_email, valid_pass)| hash_f.(valid_email + valid_pass)}
18
- end
19
-
20
- same_string( # IGNORE
21
- hash_credentials.('invalid_mail', 'valid_pass')
22
- ).('None') # COMMENT
23
- # IGNORE
24
- same_string( # IGNORE
25
- hash_credentials.('valid@domain.com', 'invalid')
26
- ).('None') # COMMENT
27
- # IGNORE
28
- same_string( # IGNORE
29
- hash_credentials.('valid@domain.com', 'valid_pass')
30
- ).('Some(2651)') # COMMENT
@@ -1,26 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'yopt/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "yopt"
8
- spec.version = Yopt::VERSION
9
- spec.authors = ["lorenzo.barasti"]
10
-
11
- spec.summary = %q{Scala-inspired Options for the idiomatic Rubyist}
12
- spec.description = %q{The Option type models the possible absence of a value. It lets us deal with the uncertainty related to such a value being there without having to resort to errors or conditional blocks.}
13
- spec.homepage = "http://lbarasti.github.io/yopt"
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
20
-
21
- spec.required_ruby_version = '>= 2.0'
22
-
23
- spec.add_development_dependency "bundler", "~> 1.10"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "minitest", "~> 5.8"
26
- end