In August 1995, Jerry Fitzpatrick posted a request on numerous Usenet groups for readers to send him a simple demonstration program written in the language of their choice. Although he explicitly requested that contributions be sent to him via e-mail, many people responded in Usenet postings. After the customary period of complaining about the crossposting and nature of the request, solutions started emerging. I was delighted to see this simple program written in a variety of languages that I usually never encounter. And of course, after a while, I sent in an APL solution. Jerry thanked me for the program, and I sent him a followup message explaining the solution in tutorial fashion. Both messages are included here.

On 7 Aug 1995, Jerry Fitzpatrick wrote:

I'm doing comparative language research for a magazine article.

You can help by writing a small "dump" program which opens a named file and displays its data in hexadecimal and ASCII format.

Here's a version written in APL. The funny symbols for which APL is famous are represented below as {keywords}. For example, {<-} is the left-facing assignment arrow.

[0] Z{<-}HEXDUMP F;B;E;H;N;P;Q;V;#IO [1] @ Hex dump of file F [2] #IO{<-}0 @ use index origin 0 [3] [4] @ Read the file (using APL*PLUS operations) [5] N{<-}-1+0{max}{max}/-#NNUMS @ available tie number [6] F #NTIE N @ open the file [7] E{<-}#NSIZE N @ file size [8] V{<-}#NREAD N,82,E,0 @ read file as character data [9] #NUNTIE N @ close the file [10] [11] @ Form the hex dump [12] Q{<-}16 @ display 16 bytes/line [13] N{<-}{ceiling}E{divide}Q @ number of lines [14] Z{<-}8 HEX Q{times}{iota}N @ addresses [15] Z{<-}(0 2+{shape}Z){take}Z @ add two blank columns [16] H{<-},' ',2 HEX #AV{iota}V @ data in hex [17] P{<-}(Q{times}3){reshape}' ' @ padding for short last row [18] H{<-}(N,Q{times}3){reshape}H,P @ turn hex display into a matrix [19] Z{<-}Z,(0 3+{shape}H){take}H @ append it to the result [20] B{<-}#AV[127],32{take}#AV @ nonprinting chars [21] V[(V{epsilon}B)/{iota}{shape}V]{<-}'.' @ change 'em to "." [22] Z{<-}Z,(N,Q){reshape}V,P @ append ASCII display to result [0] Z{<-}N HEX V [1] @ N-digit hex representation of values V [2] Z{<-}'0123456789ABCDEF'[{transpose}(N{reshape}16){represent}V] HEXDUMP'\APL.BAT' 00000000 40 65 63 68 6F 20 53 74 61 72 74 69 6E 67 20 41 @echo Starting A 00000010 50 4C 2A 50 4C 55 53 20 49 49 2F 33 38 36 2E 2E PL*PLUS II/386.. 00000020 2E 0D 0A 40 65 63 68 6F 20 6F 66 66 0D 0A 63 3A ...@echo off..c: 00000030 0D 0A 63 64 20 5C 61 70 6C 33 38 36 0D 0A 7A 69 ..cd \apl386..zi 00000040 70 70 79 35 20 65 76 6C 65 76 65 6C 3D 31 0D 0A ppy5 evlevel=1.. 00000050 63 64 20 5C 0D 0A 63 61 6C 6C 20 62 79 65 0D 0A cd \..call bye.. 00000060 65 63 68 6F 20 6F 6E 1A 0D 0A 1A echo on....Jim

I know that APL can be pretty opaque to the uninitiated, so here is an explanation of the APL program I sent you:

[0] Z{<-}HEXDUMP F;B;E;H;N;P;Q;V;#IO

This header line declares that Z is the explicit result, HEXDUMP is the function name, F is the right argument, and B, E, H, etc. are local variables.

[2] #IO{<-}0 @ use index origin 0

This causes V[0] (instead of V[1]) to index the first element of a vector, and it makes {iota}N return 0 1 2...N-1 instead of 1 2 3...N.

[4] @ Read the file (using APL*PLUS operations) [5] N{<-}-1+0{max}{max}/-#NNUMS @ available tie number [6] F #NTIE N @ open the file [7] E{<-}#NSIZE N @ file size [8] V{<-}#NREAD N,82,E,0 @ read file as character data [9] #NUNTIE N @ close the file

These operations are specific to the APL*PLUS brand of APL. Unfortunately, there's no standard method of reading files from within APL. The file is read into V as a character vector.

[11] @ Form the hex dump [12] Q{<-}16 @ display 16 bytes/line [13] N{<-}{ceiling}E{divide}Q @ number of lines [14] Z{<-}8 HEX Q{times}{iota}N @ addresses

The hex dump is built as a character matrix all at once, instead of one line at a time as would be done in most other languages. After computing N, the number of rows in the output, the addresses are generated from Q{times}{iota}N, which is 0 16 32 48 64.... The HEX subroutine returns a character matrix having the hex representation of one number in each row.

[15] Z{<-}(0 2+{shape}Z){take}Z @ add two blank columns

The {take} operation pads the right argument Z with blanks so it has the shape (number of rows and columns) specified in the left argument. {shape}Z is the current shape of Z, and 0 2+ adds zero to the number of rows and two to the number of columns.

[16] H{<-},' ',2 HEX #AV{iota}V @ data in hex

Dyadic {iota} (i.e., with two arguments) is lookup, and it's used here to convert the characters in V to their indices in #AV, the "atomic vector", which contains all 256 characters in binary order. The ' ', joins a column of blanks to the front of the 2-column matrix returned by HEX, and the comma to the right of {<-} "ravels" the matrix, changing it to a vector containing the first row followed by the second row, third row, etc. This is the hex display, but as a long vector, not a matrix.

[Actually, this statement assumes that #AV is in binary order. I know of at least one APL for which this isn't the case, so you might need to use a different variable in place of #AV. Any translation applied when reading the file must be taken into account as well.]

[17] P{<-}(Q{times}3){reshape}' ' @ padding for short last row

P is a vector containing enough blanks to fill out the short last row of the hex display.

[18] H{<-}(N,Q{times}3){reshape}H,P @ turn hex display into a matrix

H,P joins the padding onto the end of H, and {reshape} changes the result into a matrix having the shape specified by the left argument N rows and Q{times}3 columns).

[19] Z{<-}Z,(0 3+{shape}H){take}H @ append it to the result

{take} adds three columns of blanks to the end of H, and "Z," joins the result onto the end of Z.

[20] B{<-}#AV[127],32{take}#AV @ nonprinting chars

B is a vector of control characters. (Like statement [16], this also assumes that #AV is in binary/ASCII order.)

[21] V[(V{epsilon}B)/{iota}{shape}V]{<-}'.' @ change 'em to "."

V{epsilon}B produces a bit vector having an element for each character in V. 1s mark characters of V that occur in B, 0s mark chars that don't. {iota}{shape}V is a vector of the indices of V: 0 1 2 3.... The "/" is compression, which selects indices that correspond (positionwise) to the 1s in the left argument. So the expression within the brackets gives the indices of control characters in V. V[i]{<-}'.' changes the elements specified by i to periods.

[22] Z{<-}Z,(N,Q){reshape}V,P @ append ASCII display to result

{reshape} converts V padded with P (more than enough padding) to a matrix, and "Z," appends it to the result.

[0] Z{<-}N HEX V [1] @ N-digit hex representation of values V [2] Z{<-}'0123456789ABCDEF'[{transpose}(N{reshape}16){represent}V]

B{represent}V represents each element of vector V as a number in the radix specified by B (which may be different numbers, but here is just N 16s). The result is a matrix having a column for each number in V and a row for each "digit" in the representation. {transpose} swaps the rows and columns so there's a row for each number in V. Indexing '012..DEF' with this matrix produces a character matrix.

It's no trouble, so I'll pop the symbolic listing in the mail to you.

Jim

Home Page