drcabana.org
Computational divertissements


How to build a quine, reprise

Posted on

Let's revisit the quine building exercise described in the first post in this series, this time in F#. Small issues will arise, and we'll see how to handle them.

Step One

As before, we start with an empty list, and three print loops.

let data = [
 ]
for d in data do
   System.Console.WriteLine(d)
for d in data do
   System.Console.WriteLine(d)
for d in data do
   System.Console.WriteLine(d)

Notice that the closing bracket is indented one space. The indentation is required in F#.

Step Two

As before, the middle print loop is special. Let's set it up. The variable space is used for indentation, and semicolon is the element separator. Recall that python used a comma for that.

let data = [
 ]
let q = string(char 34)
let semicolon = string(char 59)
let space = string(char 32)
for d in data do
   System.Console.WriteLine(d])
for d in data do
   System.Console.WriteLine(space + q + d + q + semicolon)
for d in data do
   System.Console.WriteLine(d)

Step Three

We need to put some content into data, via copy-paste:

Here's the result

let data = [
 "let data = [";
 " ]";
 "let q = string(char 34)";
 "let semicolon = string(char 59)";
 "let space = string(char 32)";
 "for d in data do";
 "   System.Console.WriteLine(d)";
 "for d in data do";
 "   System.Console.WriteLine(space + q + d + q + semicolon)";
 "for d in data do";
 "   System.Console.WriteLine(d)";
 ]
let q = string(char 34)
let semicolon = string(char 59)
let space = string(char 32)
for d in data do
   System.Console.WriteLine(d)
for d in data do
   System.Console.WriteLine(space + q + d + q + semicolon)
for d in data do
   System.Console.WriteLine(d)

Step Four

Next we adjust the print loops and their string representations inside data. We have to be careful. Blindly copying the approach used in the earlier post will not suffice. Different programming languages use different conventions that may become relevant. For instance:

In python data[:b] does not include the bth element, but in F# data[..b] does include it, so we have to adjust for that. In python the 1st and 3rd print loops ranged over data[:boundary] and data[boundary:], a pleasant symmetry. F# requires data[..boundary-1] and data[boundary..] to achieve the same outcome. We'll see more minor linguistic differences in the next post, when the ruby, lua, and babashka languages join the party.

Here's the final result.

let data = [
 "let data = [";
 " ]";
 "let q = string(char 34)";
 "let semicolon = string(char 59)";
 "let space = string(char 32)";
 "let boundary = 1";
 "for d in data[..boundary-1] do";
 "   System.Console.WriteLine(d)";
 "for d in data do";
 "   System.Console.WriteLine(space + q + d + q + semicolon)";
 "for d in data[boundary..] do";
 "   System.Console.WriteLine(d)";
 ]
let q = string(char 34)
let semicolon = string(char 59)
let space = string(char 32)
let boundary = 1
for d in data[..boundary-1] do
   System.Console.WriteLine(d)
for d in data do
   System.Console.WriteLine(space + q + d + q + semicolon)
for d in data[boundary..] do
   System.Console.WriteLine(d)

Testing

Save the program as quine.fsx and run a diff via zsh process substitution:

❯ dotnet fsi --version
Microsoft (R) F# Interactive version 12.0.0.0 for F# 6.0

❯ diff -s quine.fsx =(dotnet fsi quine.fsx)
Files quine.fsx and /tmp/zshsHJ5T3 are identical

If you want to try this at home, make sure to call your program quine.fsx rather than quine.fs. The F# compiler considers an fs file to be program and an fsx file to be a script; it treats them differently. For our purpose the script is easier to work with.

But wait, there's more.

In the next post we introduce more languages and automate the building of the quines. This will set the stage for building quine relays in the subsequent post.

The supporting code repo is drcabana/quines.