tomstuart / monads Goto Github PK
View Code? Open in Web Editor NEWSimple Ruby implementations of some common monads.
Home Page: https://tomstu.art/refactoring-ruby-with-monads
License: MIT License
Simple Ruby implementations of some common monads.
Home Page: https://tomstu.art/refactoring-ruby-with-monads
License: MIT License
I was experimenting the use of Monads::Optional
with chains of calls that imply hash access, such as when dealing with Rails parameters:
params[:profile][:resume].tempfile.path
Initially I thought that wrapping an OpenStruct
around a Hash
would've been a good fit for Monads::Optional
but then I discovered that it doesn't work.
Here's a list of tests that I've run:
require 'monads'
require 'hashie'
require 'ostruct'
require 'action_pack'
require 'rack/test'
require 'action_controller'
params = {
foo: {
bar: 42
}
}
rails_params = ActionController::Parameters.new(params)
ostruct_params = OpenStruct.new(params)
hashie_params = Hashie::Mash.new(params)
hashie_rails_params = Hashie::Mash.new(rails_params)
ostruct_rails_params = OpenStruct.new(rails_params)
Monads::Optional.new(ostruct_params).foo
# => #<struct Monads::Optional value={:bar=>42}>
Monads::Optional.new(hashie_params).foo
# => #<struct Monads::Optional value=#<Hashie::Mash bar=42>>
Monads::Optional.new(ostruct_rails_params).foo
# => #<struct Monads::Optional value={"bar"=>42}>
Monads::Optional.new(hashie_rails_params).foo
# => #<struct Monads::Optional value=#<Hashie::Mash bar=42>>
Monads::Optional.new(ostruct_params).foo.bar
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):23
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_params).foo.bar
# => #<struct Monads::Optional value=42>
Monads::Optional.new(ostruct_rails_params).foo.bar
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):25
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_rails_params).foo.bar
# => #<struct Monads::Optional value=42>
Monads::Optional.new(ostruct_params).foo.bar.value
# NoMethodError: undefined method `bar' for {:bar=>42}:Hash
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):27
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_params).foo.bar.value
# => 42
Monads::Optional.new(ostruct_rails_params).foo.bar.value
# NoMethodError: undefined method `bar' for {"bar"=>42}:ActionController::Parameters
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `public_send'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:11:in `block in method_missing'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:5:in `block in within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:21:in `block in ensure_monadic_result'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `call'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/optional.rb:13:in `and_then'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:4:in `within'
# from /Users/olistik/.rvm/gems/ruby-2.2.2/gems/monads-0.0.1/lib/monads/monad.rb:10:in `method_missing'
# from (irb):29
# from /Users/olistik/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
Monads::Optional.new(hashie_rails_params).foo.bar.value
# => 42
It looks like that the only viable solution is to wrap params
into a Hashie::Mash
and then pass it to Monads::Optional
.
Would you like/expect to have an implementation that supports OpenStruct
and ActionController::Parameters
?
First of all, thanks for your talk about Ruby monads, it blew my mind ^^
I've been trying to imitate your logic as a TDD exercise, and was comparing what I was writing to your code and found a difference puzzling me in the "Many" monad.
When I'm trying to use yours, I find that:
Blaher = Struct.new(:blahs)
Bloher = Struct.new(:blohs)
blahers = [Blaher.new([Bloher.new([1, 2, 3, 4])])]
Monads::Many.new(blahers).blahs.blohs.values
Ends up in a:
NoMethodError: undefined method `bloh' for [#<struct bloh=[1, 2, 3, 4]>]:Array
I thought it had something to do with the Many.from_value method and tried a quick-and-dirty fix with:
def from_value(value)
Many.new(value)
end
It makes the method_missing do its job and return the subelements expected, though I find one test (spec/monads/many_spec.rb:49
the one that verifies that Many.from_value wraps the value in an array before wrapping it in the monad) fails.
I'm a bit unsure on what to do to fix this, so I thought I'd let you know ^^°
Again, thanks for the awesome talk and ideas.
Optional.new([1, 2, 3]).first # => [1, 2, 3]
Optional.new([1, 2, 3]).last # => #<struct Monads::Optional value=3>
Optional.new([1, 2, 3]).last(2) # => #<struct Monads::Optional value=[2, 3]>
Optional.new([1, 2, 3]).first(2) # => [[1, 2, 3]]
I'm not knowledgable enough about the behavior of the Maybe monad to tell if this is the correct behavior or if this is a bug.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.