Constant Shadows

The script lines feature is probably the most famous example for idiosyncratic naming in Ruby!

Ruby can save all source files you load or require as strings. This is useful for debugging utilities, for example, standard library's debug and tracer both use these capabilities.

This is possible with the script lines object: It is a Ruby hash that stores all script filenames as keys and the whole file contents as values. It is not activated by default, you have to globally opt-in for it, which is done by initializing it with an empty hash!

Quiz: How to Access this Magical Script Lines Hash?

A) script_lines
B) SCRIPT_LINES
C) __SCRIPT_LINES
D) __script_lines__
E) SCRIPT_LINES__
F) Ruby::DebugInfo::SCRIPT_LINES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
The answer is E: SCRIPT_LINES__

WEIRD. What gives with the trailing underscores? It’s such a big constant that it leaves a two-character shadow!!

— why

Usage Example

It is a best practice to check if it already is defined, because someone else might have already activated it:

SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
require 'abbrev'
SCRIPT_LINES__.size
# => 1
SCRIPT_LINES__.keys.first
# => "/home/user/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/abbrev.rb"
SCRIPT_LINES__.values.first.is_a? Array
# => true
SCRIPT_LINES__.values.first.size
# => 131

Running puts SCRIPT_LINES__.values.first will output every line of the file:

#--
# Copyright (c) 2001,2003 Akinori MUSHA <knu@iDaemons.org>
#
# All rights reserved.  You can redistribute and/or modify it under
# the same terms as Ruby.
#
# $Idaemons: /home/cvs/rb/abbrev.rb,v 1.2 2001/05/30 09:37:45 knu Exp $
# $RoughId: abbrev.rb,v 1.4 2003/10/14 19:45:42 knu Exp $
# $Id: abbrev.rb 46784 2014-07-11 08:16:05Z hsbt $
#++

##
# Calculates the set of unambiguous abbreviations for a given set of strings.
#
#   require 'abbrev'
#   require 'pp'
#
#   pp Abbrev.abbrev(['ruby'])
#   #=>  {"ruby"=>"ruby", "rub"=>"ruby", "ru"=>"ruby", "r"=>"ruby"}
#
#   pp Abbrev.abbrev(%w{ ruby rules })
#
# _Generates:_
#   { "ruby"  =>  "ruby",
#     "rub"   =>  "ruby",
#     "rules" =>  "rules",
#     "rule"  =>  "rules",
#     "rul"   =>  "rules" }
#
# It also provides an array core extension, Array#abbrev.
#
#   pp %w{ summer winter }.abbrev
#
# _Generates:_
#   { "summer"  => "summer",
#     "summe"   => "summer",
#     "summ"    => "summer",
#     "sum"     => "summer",
#     "su"      => "summer",
#     "s"       => "summer",
#     "winter"  => "winter",
#     "winte"   => "winter",
#     "wint"    => "winter",
#     "win"     => "winter",
#     "wi"      => "winter",
#     "w"       => "winter" }

module Abbrev

  # Given a set of strings, calculate the set of unambiguous abbreviations for
  # those strings, and return a hash where the keys are all the possible
  # abbreviations and the values are the full strings.
  #
  # Thus, given +words+ is "car" and "cone", the keys pointing to "car" would
  # be "ca" and "car", while those pointing to "cone" would be "co", "con", and
  # "cone".
  #
  #   require 'abbrev'
  #
  #   Abbrev.abbrev(%w{ car cone })
  #   #=> {"ca"=>"car", "con"=>"cone", "co"=>"cone", "car"=>"car", "cone"=>"cone"}
  #
  # The optional +pattern+ parameter is a pattern or a string. Only input
  # strings that match the pattern or start with the string are included in the
  # output hash.
  #
  #   Abbrev.abbrev(%w{car box cone crab}, /b/)
  #   #=> {"box"=>"box", "bo"=>"box", "b"=>"box", "crab" => "crab"}
  #
  #   Abbrev.abbrev(%w{car box cone}, 'ca')
  #   #=> {"car"=>"car", "ca"=>"car"}
  def abbrev(words, pattern = nil)
    table = {}
    seen = Hash.new(0)

    if pattern.is_a?(String)
      pattern = /\A#{Regexp.quote(pattern)}/  # regard as a prefix
    end

    words.each do |word|
      next if word.empty?
      word.size.downto(1) { |len|
        abbrev = word[0...len]

        next if pattern && pattern !~ abbrev

        case seen[abbrev] += 1
        when 1
          table[abbrev] = word
        when 2
          table.delete(abbrev)
        else
          break
        end
      }
    end

    words.each do |word|
      next if pattern && pattern !~ word

      table[word] = word
    end

    table
  end

  module_function :abbrev
end

class Array
  # Calculates the set of unambiguous abbreviations for the strings in +self+.
  #
  #   require 'abbrev'
  #   %w{ car cone }.abbrev
  #   #=> {"car"=>"car", "ca"=>"car", "cone"=>"cone", "con"=>"cone", "co"=>"cone"}
  #
  # The optional +pattern+ parameter is a pattern or a string. Only input
  # strings that match the pattern or start with the string are included in the
  # output hash.
  #
  #   %w{ fast boat day }.abbrev(/^.a/)
  #   #=> {"fast"=>"fast", "fas"=>"fast", "fa"=>"fast", "day"=>"day", "da"=>"day"}
  #
  #   Abbrev.abbrev(%w{car box cone}, "ca")
  #   #=> {"car"=>"car", "ca"=>"car"}
  #
  # See also Abbrev.abbrev
  def abbrev(pattern = nil)
    Abbrev::abbrev(self, pattern)
  end
end

Memory Impact

Only use this for debugging purpose, since a lot of strings get loaded into your memory. This is a comparison loading ActiveSupport with and without script lines:

require 'active_support/all'
puts "#{`ps -o rss -p #{Process.pid}`.strip.split.last.to_i / 1024.0} MB"

Result: 16.421875 MB

SCRIPT_LINES__ = {}
require 'active_support/all'
puts "#{`ps -o rss -p #{Process.pid}`.strip.split.last.to_i / 1024.0} MB"

Result: 19.85546875 MB

Resources

More Idiosyncratic Ruby