# Deterministic
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/pzol/deterministic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Gem Version](https://badge.fury.io/rb/deterministic.png)](http://badge.fury.io/rb/deterministic)
Deterministic is to help your code to be more confident, by utilizing functional programming patterns.
This is a spiritual successor of the [Monadic gem](http://github.com/pzol/monadic). The goal of the rewrite is to get away from a bit too forceful approach I took in Monadic, especially when it comes to coercing monads, but also a more practical but at the same time more strict adherence to monad laws.
## Patterns
Deterministic provides different monads, here is a short guide, when to use which
#### Result: Success & Failure
- an operation which can succeed or fail
- the result (content) of of the success or failure is important
- you are building one thing
- chaining: if one fails (Failure), don't execute the rest
#### Option: Some & None
- an operation which returns either some result or nothing
- in case it returns nothing it is not important to know why
- you are working rather with a collection of things
- chaining: execute all and then select the successful ones (Some)
#### Either: Left & Right
- an operation which returns several good and bad results
- the results of both are important
- chaining: if one fails, continue, the content of the failed and successful are important
#### Maybe
- an object may be nil, you want to avoid endless nil? checks
#### Enums (Algebraic Data Types)
- roll your own pattern
## Usage
### Result: Success & Failure
```ruby
Success(1).to_s # => "1"
Success(Success(1)) # => Success(1)
Failure(1).to_s # => "1"
Failure(Failure(1)) # => Failure(1)
```
Maps a `Result` with the value `a` to the same `Result` with the value `b`.
```ruby
Success(1).fmap { |v| v + 1} # => Success(2)
Failure(1).fmap { |v| v - 1} # => Failure(0)
```
Maps a `Result` with the value `a` to another `Result` with the value `b`.
```ruby
Success(1).bind { |v| Failure(v + 1) } # => Failure(2)
Failure(1).bind { |v| Success(v - 1) } # => Success(0)
```
Maps a `Success` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Success`.
```ruby
Success(1).map { |n| Success(n + 1) } # => Success(2)
Failure(0).map { |n| Success(n + 1) } # => Failure(0)
```
Maps a `Failure` with the value `a` to another `Result` with the value `b`. It works like `#bind` but only on `Failure`.
```ruby
Failure(1).map_err { |n| Success(n + 1) } # => Success(2)
Success(0).map_err { |n| Success(n + 1) } # => Success(0)
```
```ruby
Success(0).try { |n| raise "Error" } # => Failure(Error)
```
Replaces `Success a` with `Result b`. If a `Failure` is passed as argument, it is ignored.
```ruby
Success(1).and Success(2) # => Success(2)
Failure(1).and Success(2) # => Failure(1)
```
Replaces `Success a` with the result of the block. If a `Failure` is passed as argument, it is ignored.
```ruby
Success(1).and_then { Success(2) } # => Success(2)
Failure(1).and_then { Success(2) } # => Failure(1)
```
Replaces `Failure a` with `Result`. If a `Failure` is passed as argument, it is ignored.
```ruby
Success(1).or Success(2) # => Success(1)
Failure(1).or Success(1) # => Success(1)
```
Replaces `Failure a` with the result of the block. If a `Success` is passed as argument, it is ignored.
```ruby
Success(1).or_else { Success(2) } # => Success(1)
Failure(1).or_else { |n| Success(n)} # => Success(1)
```
Executes the block passed, but completely ignores its result. If an error is raised within the block it will **NOT** be catched.
Try failable operations to return `Success` or `Failure`
```ruby
include Deterministic::Prelude::Result
try! { 1 } # => Success(1)
try! { raise "hell" } # => Failure(#<RuntimeError: hell>)
```
### Result Chaining
You can easily chain the execution of several operations. Here we got some nice function composition.
The method must be a unary function, i.e. it always takes one parameter - the context, which is passed from call to call.
The following aliases are defined
```ruby
alias :>> :map
alias :<< :pipe
```
This allows the composition of procs or lambdas and thus allow a clear definiton of a pipeline.
```ruby
Success(params) >>
validate >>
build_request << log >>
send << log >>
build_response
```
#### Complex Example in a Builder Class
```ruby
class Foo
include Deterministic
alias :m :method # method conveniently returns a Proc to a method
def call(params)
Success(params) >> m(:validate) >> m(:send)
end
def validate(params)
# do stuff
Success(validate_and_cleansed_params)
end
def send(clean_params)
# do stuff
Success(result)
end
end
Foo.new.call # Success(3)
```
Chaining works with blocks (`#map` is an alias for `#>>`)
```ruby
Success(1).map {|ctx| Success(ctx + 1)}
```
it also works with lambdas
```ruby
Success(1) >> ->(ctx) { Success(ctx + 1) } >> ->(ctx) { Success(ctx + 1) }
```
and it will break the chain of execution, when it encounters a `Failure` on its way
```ruby
def works(ctx)
Success(1)
end
def breaks(ctx)
Failure(2)
end
def never_executed(ctx)
Success(99)
end
Success(0) >> method(:works) >> method(:breaks) >> method(:never_executed) # Failure(2)
```
`#map` aka `#>>` will not catch any exceptions raised. If you want automatic exception handling, the `#try` aka `#>=` will catch an error and wrap it with a failure
```ruby
def error(ctx)
raise "error #{ctx}"
end
Success(1) >= method(:error) # Failure(RuntimeError(error 1))
```
### Chaining with #in_sequence
When creating long chains with e.g. `#>>`, it can get cumbersome carrying
around the entire context required for every function within the chain. Also,
every function within the chain requires some boilerplate code for extracting the
relevant information from the context.
Similarly to, for example, the `do` notation in Haskell and _sequence
comprehensions_ or _for comprehensions_ in Scala, `#in_sequence` can be used to
streamline the same process while keeping the code more readable. Using
`#in_sequence` provides all the benefits of using the `Result` monad while
still allowing to write code that reads very much like standard imperative
Ruby.
Here's an example:
```ruby
class Foo
include Deterministic::Prelude
def call(input)
in_sequence do
get(:sanitized_input) { sanitize(input) }
and_then { validate(sanitized_input) }
get(:user) { get_user_from_db(sanitized_input) }
let(:name) { user.fetch(:name) }
observe { log('user name', name) }
get(:request) { build_request(sanitized_input, user) }
observe { log('sending request', request) }
get(:response) { send_request(request) }
observe { log('got response', response) }
and_yield { format_response(response) }
end
end
def sanitize(input)
sanitized_input = input
Success(sanitized_input)
end
def validate(sanitized_input)
Success(sanitized_input)
end
def get_user_from_db(sanitized_input)
Success(type: :admin, id: sanitized_input.fetch(:id), name: 'John')
end
def build_request(sanitized_input, user)
Success(input: sanitized_input, user: user)
end
def log(message, data)
# logger.info(message, data)
end
def send_request(request)
Success(status: 200)
end
def format_response(response)
Success(response: response, message: 'it worked')
end
end
Foo.new.call(id: 1)
```
Notice how the functions don't necessarily have to accept only a single
argument (`build_request` accepts 2). Also notice how the meth
没有合适的资源?快使用搜索试试~ 我知道了~
函数式-确定性-Ruby取笑___下载.zip
共50个文件
rb:41个
md:2个
txt:1个
1.该资源内容由用户上传,如若侵权请联系客服进行举报
2.虚拟产品一经售出概不退款(资源遇到问题,请及时私信上传者)
2.虚拟产品一经售出概不退款(资源遇到问题,请及时私信上传者)
版权申诉
0 下载量 28 浏览量
2023-04-18
00:20:15
上传
评论
收藏 43KB ZIP 举报
温馨提示
函数式-确定性-Ruby取笑___下载.zip
资源推荐
资源详情
资源评论
收起资源包目录
函数式-确定性-Ruby取笑___下载.zip (50个子文件)
deterministic-master
HISTORY.md 934B
lib
deterministic
result.rb 2KB
null.rb 965B
maybe.rb 126B
enum.rb 6KB
sequencer.rb 4KB
option.rb 1KB
match.rb 2KB
core_ext
result.rb 291B
object
result.rb 121B
monad.rb 2KB
version.rb 46B
either.rb 659B
protocol.rb 3KB
deterministic.rb 305B
Rakefile 117B
.github
workflows
ci.yml 460B
LICENSE.txt 1KB
Guardfile 495B
spec
lib
deterministic
result_spec.rb 2KB
sequencer_spec.rb 16KB
protocol_spec.rb 816B
monad_spec.rb 1KB
monad_axioms.rb 1KB
class_mixin_spec.rb 463B
core_ext
result_spec.rb 656B
object
either_spec.rb 295B
currify_spec.rb 1KB
maybe_spec.rb 468B
null_spec.rb 1KB
option_spec.rb 4KB
either_spec.rb 698B
result
success_spec.rb 1KB
failure_spec.rb 2KB
result_map_spec.rb 3KB
result_shared.rb 657B
enum_spec.rb 3KB
readme_spec.rb 1KB
examples
logger_spec.rb 3KB
config_spec.rb 2KB
validate_address_spec.rb 1KB
amount_spec.rb 1KB
list.rb 3KB
controller_spec.rb 1KB
list_spec.rb 5KB
spec_helper.rb 907B
Gemfile 86B
deterministic.gemspec 1KB
.gitignore 154B
README.md 17KB
共 50 条
- 1
资源评论
快撑死的鱼
- 粉丝: 1w+
- 资源: 9156
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功