TRANSCODE Explorations into the Code Transcendental.

Code Identity

A Ruby Quiz once asked us to print “Hello, world!” in an unusual way. Being an over-achiever-to-a-fault, I wondered whether I could make the computer invent the alternatives for me. Why come up with one peculiar solution when a little program could keep coming up with more?

The idea was to define code identities: transformations that change an expression’s form while preserving something we care about. For a string, reversing it twice gives us the same characters. Splitting it into characters and joining them again does too.

"Hello, World!".reverse.reverse
"Hello, World!".chars.join

Neither is an improvement over the original string. That’s beside the point. We’re generating variations, not optimizing them. In fact, the pointless extra work is part of the fun.

What stays the same?

Before generating anything, we need to decide what counts as a solution. Here, a program must write the requested UTF-8 text to standard output, write nothing to standard error, and exit successfully. Newlines are part of the text. The default is "Hello, World!\n".

This deliberately narrow contract lets us use print, $stdout.write, and printf("%s", ...) as different output forms. Their return values need not agree: write returns a byte count, for instance. We are comparing standalone programs by their output and exit status, not claiming that these calls can replace one another in every Ruby expression.

Likewise, puts isn’t interchangeable with print for arbitrary text: puts can add a newline. Rather than hide that difference, we’ll make the requested bytes explicit.

Growing a family of expressions

Our starting point is a string literal. Ruby’s String#dump produces its quoted Ruby representation, including escapes. A quote, backslash, newline, or interpolation-looking sequence in the text remains data.

We then grow new expressions from the literal. Each expression can be wrapped in .reverse.reverse or .chars.join; those expressions can be wrapped again. The parentheses matter: each rule applies to the whole expression it receives.

require 'set'
require 'open3'
require 'rbconfig'

class CodeIdentity
  RULES = [
    ->(expression) { "(#{expression}).reverse.reverse" },
    ->(expression) { "(#{expression}).chars.join" }
  ].freeze

  def initialize(text, rules: RULES)
    unless text.is_a?(String) && text.encoding == Encoding::UTF_8 && text.valid_encoding?
      raise ArgumentError, 'text must be a valid UTF-8 string'
    end

    @literal = text.dump
    @rules = rules.dup.freeze
  end

  def original
    "print(#{@literal})"
  end

  def generate(limit = 20)
    unless limit.is_a?(Integer) && limit.positive?
      raise ArgumentError, 'limit must be a positive integer'
    end

    queue = [@literal]
    seen = Set.new(queue)
    programs = []
    cursor = 0

    while cursor < queue.length
      expression = queue[cursor]
      cursor += 1

      forms = [
        "print(#{expression})",
        "$stdout.write(#{expression})",
        "printf(\"%s\", #{expression})"
      ]
      forms.each do |source|
        programs << source
        return programs if programs.length == limit
      end

      @rules.each do |rule|
        candidate = rule.call(expression)
        queue << candidate if seen.add?(candidate)
      end
    end

    programs
  end

end

The queue makes this a breadth-first search through expression forms. A set remembers which forms have already been seen, so a rule that returns its input, or a cycle between two forms, does not keep the search alive forever. If the queue runs out, generate returns however many programs it found. If the rules keep producing new forms, the requested limit stops it. The default rules can keep nesting, so there is no fixed number of distinct source strings they can produce—although this particular search always asks for a finite number.

This is deliberately a generator for a small family of string expressions. It accepts text, not arbitrary Ruby source. We aren’t searching through unrelated programs or using a regular expression to guess where Ruby string literals begin and end. The rule functions are code we supply ourselves; they must return Ruby expressions with the intended string value.

Putting the candidates on trial

The generation rules give us a reason to expect the same output. Running the programs gives us a way to catch mistakes in the implementation.

Each candidate runs in a separate Ruby process. That keeps its globals and output streams separate from the program doing the checking. Ruby’s Open3.capture3 collects standard output, standard error, and process status. Binary capture lets us compare the output bytes directly.

require 'open3'
require 'rbconfig'

class CodeIdentity
  def observe(source)
    stdout, stderr, status = Open3.capture3(
      RbConfig.ruby, '-EUTF-8:UTF-8', '-e', source, binmode: true
    )
    [stdout, stderr, status.exitstatus, status.termsig]
  end

  def verify!(programs)
    expected = observe(original)
    programs.each do |source|
      unless observe(source) == expected
        raise "Output mismatch for: #{source}"
      end
    end
    true
  end
end

These methods reopen the same CodeIdentity class. They compare each candidate with the literal-printing baseline, including whether the process exited normally or was terminated by a signal.

There is an important boundary here. Matching these observations is evidence about these runs, not a proof that two arbitrary programs are equivalent. Return values, allocations, object identity, timing, files, and other side effects are outside the comparison. Even our string transformations make new objects. Apply them to an object with different method behavior and the reasoning must start again.

Hello, World, nine ways

The complete script includes a small command-line interface. From the directory containing it:

ruby code_identity.rb -n 9 --check

It produces:

print("Hello, World!\n")
$stdout.write("Hello, World!\n")
printf("%s", "Hello, World!\n")
print(("Hello, World!\n").reverse.reverse)
$stdout.write(("Hello, World!\n").reverse.reverse)
printf("%s", ("Hello, World!\n").reverse.reverse)
print(("Hello, World!\n").chars.join)
$stdout.write(("Hello, World!\n").chars.join)
printf("%s", ("Hello, World!\n").chars.join)

The checker also reports Verified 9 programs. on standard error. That is output from the checker itself, not from the generated candidates.

To generate programs for different text, pass one quoted argument:

ruby code_identity.rb -n 20 --check 'Something else entirely.'

The script and its tests were checked with Ruby 3.4.9. The tests cover quotes, backslashes, literal interpolation syntax, percent signs, Unicode, NUL bytes, and different newline endings. They also check rule exhaustion, duplicate and cyclic rules, exact limits, and candidates that print the wrong output, write to standard error, or exit unsuccessfully.

What interests me most is the separation between three jobs: describing transformations, exploring their combinations, and deciding which results count as equivalent. This little toy gives each job a place. To move beyond strings, we’d need to define another domain and transformations whose assumptions we can state just as clearly.

Other contestants may have found more ingenious individual solutions. I’ve built a machine for producing ever more elaborate ways to say the same thing. Whether that is a triumph of programming or an elaborate waste of clock cycles, I’ll leave to you. ;)