Hardscrabble 🍫

By Max Jacobson

how to re-draw the line you just printed in Ruby, like to make a progress bar

14 Dec 2015

Here’s something I learned recently. Let’s say you have a program that is going to take a long time, and you want to mark the progress over time. You can print out some information like this:

tasks = Array.new(1000)
tasks.each.with_index do |task, index|
  sleep rand(0..0.1) # (something slow)
  percentage = (index + 1) / tasks.count.to_f
  puts "#{(percentage * 100).round(1)}%"
end

Which looks kinda like this:

progress bar before picture

Which is, let’s say, serviceable, but not, let’s say, beautiful. It stinks that it printed out all those lines when it didn’t really need to. I would rather it had sort of animated while it went. But how is this done?

This is one of those questions that’s itched at the back of my mind for a while and which, when I finally googled it, was a bit disappointing. It’s just another unix escape character, like \n (which prints a new line). It’s \r, which I now think of as “the backspace to the beginning of the line” magic character.

Armed with this knowledge and some clunky math we can write something like this:

begin
  tasks = Array.new(1000)
  tasks.each.with_index do |task, i|
    width = `tput cols`.to_i
    sleep rand(0..0.1) # (something slow)
    percentage = (i + 1) / tasks.count.to_f
    summary = "#{(percentage * 100).round(1)}% ".rjust("100.0% ".length)
    remaining_chars_for_progress_bar = width - summary.length - 2
    chunks = (percentage * remaining_chars_for_progress_bar).ceil
    spaces = remaining_chars_for_progress_bar - chunks
    bar = "\r#{summary}[#{ '#' * chunks }#{' ' * spaces}]"
    print bar
  end
rescue Interrupt
  system "say 'I was almost done, jeez'" if RUBY_PLATFORM.include?("darwin")
end

progress bar after gif

Probably you shouldn’t use this – there’s a very nice gem called ruby-progressbar which will work across platforms and lets you customize some things. But it’s nice information to have, because now you can do things like this:

barnyard

I’ll leave it as an exercise to the reader how to write this one.

how to tell ruby how to compare numbers to your object with coerce

09 Nov 2015

Let’s say you have some object that represents some numeric idea:

class CupsOfCoffeePerDay
  include Comparable

  MY_LIMIT = 3

  def initialize(num)
    @num = num
  end

  def <=>(other)
    num <=> other
  end

  def risky?(threshold: MY_LIMIT)
    self > threshold
  end
end

CupsOfCoffeePerDay.new(4).risky? #=> true
CupsOfCoffeePerDay.new(4) > 5 #=> false

This object takes in a number and wraps it, and then extends it with some domain-specific logic. Specifically, it represents the idea that there is a threshold to how many cups of coffee an individual can have per day before it becomes risky.

It’s neat that we’re able to compare our custom ruby object to a plain number. All we had to do was include Comparable and then implement the <=> method (also known as “the spaceship operator”) to define how we’d like our object to compare to numbers. We’d like to expose the internal num value, and use that when comparing.

The neat thing is that we get all the comparing methods for free.

We’re not quite done yet, though. Watch what happens when we try to do this:

CupsOfCoffeePerDay.new(4) > CupsOfCoffeePerDay.new(5)

I get this error when I run the program:

app.rb:27:in `>': comparison of CupsOfCoffeePerDay with CupsOfCoffeePerDay failed (ArgumentError)
        from app.rb:27:in `<main>'

What’s happening here?

  1. we create two objects
  2. we ask one object if it’s greater than the second object
  3. our implementation refers to the wrapped number value (num, which is just a Fixnum) and asks it if it’s greater than this second object
  4. the fixnum complains that it doesn’t know how to compare itself to some ranom object

And, fair enough. From the point of view of the number, it has no idea what cups of coffee per day even means.

We could change our implementation to accomodate this use-case:

class CupsOfCoffeePerDay
  include Comparable

  MY_LIMIT = 3

  def initialize(num)
    @num = num
  end

  def <=>(other)
    if other.is_a?(CupsOfCoffeePerDay)
      num <=> other.num
    else
      num <=> other
    end
  end

  def risky?(threshold: MY_LIMIT)
    self > threshold
  end

  protected

  attr_reader :num
end

Note that we had to add those last few lines to make it easier to access the num from outside an instance of CupsOfCoffeePerDay.

This is not bad.

That attribute is marked as protected because so far we can only imagine it being necessary to be used by other instances of CupsOfCoffeePerDay, for the sake of comparison.

(I remember having a long and horrified conversation with a coworker when neither of us could come up with a scenario where you would use protected over private, but it turns out that this is precisely the situation where you would.)

But look what happens when you try this:

4 > CupsOfCoffeePerDay.new(5)

Or this:

[
  CupsOfCoffeePerDay.new(4),
  3,
  CupsOfCoffeePerDay.new(1)
].sort

When I try these, I get errors like this:

app.rb:32:in `>': comparison of Fixnum with CupsOfCoffeePerDay failed (ArgumentError)
        from app.rb:32:in `<main>'

Is there anything we can do to avoid these errors? I think one, strong argument is that we shouldn’t try to. Rather, we should audit our system and make sure that we never mix-and-match our types. If we can do that, that’s probably for the best.

Except… this is Ruby, and Ruby always has another trick up its sleeve.

Check it:

class CupsOfCoffeePerDay
  include Comparable

  MY_LIMIT = 3

  def initialize(num)
    @num = num
  end

  def <=>(other)
    if other.is_a?(CupsOfCoffeePerDay)
      num <=> other.num
    else
      num <=> other
    end
  end

  def risky?(threshold: MY_LIMIT)
    self > threshold
  end

  def coerce(other)
    [CupsOfCoffeePerDay.new(other), self]
  end

  protected

  attr_reader :num
end

There’s not a ton of documentation about this. I only found it by luck. I was looking to understand how Ruby numbers does its comparisons, and I opened up pry (with pry-doc installed), and started exploring:

$ gem install pry pry-doc
$ pry
> 4.pry
(4)> show-source >
From: numeric.c (C Method):
Owner: Fixnum
Visibility: public
Number of lines: 17

static VALUE
fix_gt(VALUE x, VALUE y)
{
    if (FIXNUM_P(y)) {
        if (FIX2LONG(x) > FIX2LONG(y)) return Qtrue;
        return Qfalse;
    }
    else if (RB_TYPE_P(y, T_BIGNUM)) {
        return FIX2INT(rb_big_cmp(rb_int2big(FIX2LONG(x)), y)) > 0 ? Qtrue : Qfalse;
    }
    else if (RB_TYPE_P(y, T_FLOAT)) {
        return rb_integer_float_cmp(x, y) == INT2FIX(1) ? Qtrue : Qfalse;
    }
    else {
        return rb_num_coerce_relop(x, y, '>');
    }
}

At this point, I thought oh no! C!

But like, this is so cool: this is the implementation of the greater than method in numbers in Ruby, and it’s totally discoverable if you open pry and ask it to show-source.

I don’t really know C, but if I squint, I can tell that this is doing something kind of reasonable. It seems to be checking the type of the second value (the one you’re comparing the current value to) and doing something different based on the type. The final branch of logic is when the type is unknown. Bingo. Our CupsOfCoffeePerDay type is definitely unknown. In that case, it calls rb_num_coerce_relop. Unfortunately, when I asked pry to show-source rb_num_coerce_relop it didn’t know how.

Thankfully, it printed the filename this source code can be found in, so I went to the ruby source code and searched for a file called numeric.c. Within that, I searched for the rb_num_coerce_relop function. It takes in the two objects (the CupsOfCoffeePerDay and the number) and the operator (>). Its source looks like this:

VALUE
rb_num_coerce_relop(VALUE x, VALUE y, ID func)
{
    VALUE c, x0 = x, y0 = y;

    if (!do_coerce(&x, &y, FALSE) ||
	NIL_P(c = rb_funcall(x, func, 1, y))) {
	rb_cmperr(x0, y0);
	return Qnil;		/* not reached */
    }
    return c;
}

What does that do? It looks like it coerces the two types to be the same type, and then calls the > function on the first one, passing the second one. (Again: squinting).

So do_coerce is where the interesting part happens. I’ll just link to it because it’s pretty long. But the cool thing in it is that it checks if the first object implements a coerce method, and if it does, it does something different. So then it becomes a game of figuring out how to write a coerce method and finding out, via stack overflow (of course), that you can add this magic coerce method, and it will take in the second object, and it’s expected to return an array of compatible types, with the second object’s value first, and the first object’s value second.

So. Now that we know about coerce, our objects can be really simple, but they can still be compared bidirectionally.

begin rescue else

20 Oct 2015

Quick ruby tip kinda post.

Today I learned, this is a valid Ruby program:

begin
  raise if [true, false].sample
rescue
  puts "failed"
else
  puts "did not fail"
end

I’m used to using else after an if, but not after a rescue. This is like saying “do this thing. if it fails, do this other thing. if it doesn’t fail, do this other, other thing.

Huh!

(Via rails)