Project

wrapped

0.02
No commit activity in last 3 years
No release in over 3 years
The unchecked nil is a dangerous concept leading to NoMethodErrors at runtime. It would be better if you were forced to explictly unwrap potentially nil values. This library provides mechanisms and convenience methods for making this more possible.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
~> 3.1.0
 Project Readme

Wrapped

This gem is a tool you can use while developing your API to help consumers of your code to find bugs earlier and faster. It works like this: any time you write a method that could produce nil, you instead write a method that produces a wrapped value.

Warning: Experimental

This library is in an early, experimental phase and may change suddenly and drastically. Backwards-compatibility should not be expected between releases, and the project may be cancelled at any time.

Example

Here's an example along with how it can help with errors:

Say you have a collection of users along with a method for accessing the first user:

class UserCollection
  def initialize(users)
    @users = users
  end

  def first_user
    @users.first
  end
end

Now your friend uses your awesome UserCollection code:

class FriendGroups
  def initialize(user_collections)
    @user_collections = user_collections
  end

  def first_names
    @user_collections.map do |user_collection|
      user_collection.first_user.first_name
    end
  end
end

And then she tries it:

FriendGroups.new([UserCollection.new([])]).first_names

... and it promptly blows up:

NoMethodError: undefined method `first_name' for nil:NilClass
        from (irb):52:in `first_names'
        from (irb):51:in `map'
        from (irb):51:in `first_names'
        from (irb):57

But that's odd; UserCollection definitely has a #first_names method, and we definitely passed a UserCollection, and ... oooh, we passed no users, and so we got nil.

Right.

Instead what you want to do is wrap it. Wrap that nil. Make the user know that they have to consider the result.

class UserCollection
  def initialize(users)
    @users = users
  end

  def first_user
    @users.first.wrapped
  end
end

Now in your documentation you explain that it produces a wrapped value. And people who skip documentation and instead read source code will see that it is wrapped.

So they unwrap it, because they must. They can't even get a happy path without unwrapping it.

class FriendGroups
  def initialize(user_collections)
    @user_collections = user_collections
  end

  def first_names
    @user_collections.map do |user_collection|
      user_collection.first_user.unwrap_or('') {|user| user.first_name }
    end
  end
end

Cool Stuff

A wrapped value mixes in Enumerable. The functional world would say "that's a functor!". They're close enough.

This means that you can map, inject, to_a, any?, and so on over your wrapped value. By wrapping it you've just made it more powerful!

For example:

irb(main):054:0> 1.wrapped.inject(0) {|_, n| n+1}
=> 2
irb(main):055:0> nil.wrapped.inject(0) {|_, n| n+1}
=> 0

And then we have flat_map, which you can use to produce another wrapped object:

irb> 1.wrapped.flat_map {|n| (n + 1).wrapped}.flat_map {|n| (n*2).wrapped}.unwrap
=> 4

Those same people who will exclaim things about functors will, at this point, get giddy about monads. I mean, they're right, but they can relax. It's just a monad.

Those people ("what do you mean, 'those people'?!") may prefer the fmap method:

irb> 1.wrapped.fmap {|n| n+1}.unwrap_or(0) {|n| n+4}
=> 6

Other Methods

Then we added some convenience methods to all of this. Here's a tour:

irb> require 'wrapped'
=> true
irb> 1.wrapped.unwrap_or(-1)
=> 1
irb> nil.wrapped.unwrap_or(-1)
=> -1
irb> 1.wrapped.present {|n| p n }.blank { puts "nothing!" }
1
=> #<Present:0x7fc570aed0e8 @value=1>
irb> nil.wrapped.present {|n| p n }.blank { puts "nothing!" }
nothing!
=> #<Blank:0x7fc570ae21c0>
irb> 1.wrapped.unwrap
=> 1
irb> nil.wrapped.unwrap
IndexError: Blank has no value
	from /home/mike/wrapped/lib/wrapped/types.rb:43:in `unwrap'
	from (irb):7
irb> 1.wrapped.present?
=> true
irb> nil.wrapped.present?
=> false
irb> nil.wrapped.blank?
=> true
irb> 1.wrapped.blank?
=> false
irb> 1.wrapped.unwrap_or(0) {|n| n * 100}
=> 100
irb> nil.wrapped.unwrap_or(0) {|n| n * 100}
=> 0

Inspiration

Inspired by a conversation about a post on the thoughtbot blog titled If you gaze into nil, nil gazes also into you.

Most ideas are from Haskell and Scala. This is not new: look into the maybe functor or the option class for more.

Copyright

Copyright 2011 Mike Burns. Copyright 2014 thoughtbot.