時代遅れひとりFizzBuzz祭り REXX編

時代遅れひとりFizzBuzz祭り、今回はREXX。正確にはオープンソースな実装で且つREXXの亜種のような存在*1らしいReginaだ。

REXXは元々IBMで開発されたという経緯もあり、メインフレームの世界では主要なスクリプト言語らしい。同時にIBM PC-DOSOS/2にもバンドルされていたという歴史もある。IBMではないが、Amiga OSにもバンドルされていたらしい。

IBM PC-DOSOS/2でのREXXの役割は、強力なスクリプト言語という側面だけでなく、バッチファイルの代用という面もあったようだ。この点が前回のPowerShellと共通していると言えるが、それだけではない。

PowerShellの世界ではデータは全てオブジェクトで、コマンドレットのパラメータ指定方法は統一されている。この統一感は素晴らしいと個人的に思っているのだが、その反面コマンドプロンプト時代からの既存資産を使おうとした時、統一感が少し崩れてしまう。PowerShell以前はデータは全て文字列だったし、コマンドのパラメータ指定方法はバラバラだ。この辺りが、何となくソースコードの見た目に違和感を生じさせてしまう。

この点はREXXにも通じるものがある。REXXは独自のコマンド/文法体系を持っているので、コマンドプロンプトのコマンドのような外部ツールを呼び出す部分はREXX本来の文法とは異なるものになる。

それでもPowerShellREXXは他のポピュラーなスクリプト言語よりも遥かに簡単に外部ツールを呼び出し、実行結果を活用することができる。

ついでに言うと、REXXにはオブジェクト指向プログラミングに対応したObject REXXがあるし、PowerShell.NET Frameworkとの関係からオブジェクト指向プログラミング言語的で、その点も共通項のような気がしないでもないが、今回はReginaでClassic REXXするので除外しておく。ちなみにRegina 3.5だ。

まずはCプログラマ的発想でREXXの文法で書いてみた最初のバージョン。

/* fizzbuzz1.rexx  FizzBuzz program for REXX, 1st edition */

do i = 1 to 100
  if i//15 = 0 then
    say 'FizzBuzz'
  else if i//3 = 0 then
    say 'Fizz'
  else if i//5 = 0 then
    say 'Buzz'
  else
    say i
end

exit 0

当初IBMのマニュアル中のサンプルコードのスタイルを真似ようと思ったのだが、演算子の前後にスペースがない点が見辛かったので、そこは変更している。

剰余演算子の記号が、C言語系のプログラマにとっては意表を突かれる所かもしれない*2

REXXには関数――というかプロシージャがあるので、処理を分割するのは簡単だ。

/* fizzbuzz2.rexx  FizzBuzz program for REXX, 2nd edition */

do i = 1 to 100
  call fizzbuzz i
end

exit 0

fizzbuzz: procedure
  parse arg i
  if i//15 = 0 then
    say 'FizzBuzz'
  else if i//3 = 0 then
    say 'Fizz'
  else if i//5 = 0 then
    say 'Buzz'
  else
    say i

プロシージャの定義はラベルと似ている。procedureを付けないと単なるラベルと見なされ、変数のスコープは外部と同じになる。プロシージャなら、変数はそのプロシージャ内でのみ通用するローカル変数となる。

当然ながらプロシージャは値を返すこともできる。

/* fizzbuzz3.rexx  FizzBuzz program for REXX, 3rd edition */

do i = 1 to 100
  say fizzbuzz(i)
end

exit 0

fizzbuzz: procedure
  parse arg i
  if i//15 = 0 then
    return 'FizzBuzz'
  else if i//3 = 0 then
    return 'Fizz'
  else if i//5 = 0 then
    return 'Buzz'
  else
    return i

先程とはfizzbuzzの呼び出し方が異なっている点に注意。

REXXの文法的に誤解を招く言い方かもしれないが、REXXにはグローバル変数は無いようだ。少なくともC言語などのように気軽にアクセスできる上位階層の変数は無い。上位階層の変数にアクセスしたい場合は、exposeを使って該当する変数を明示的に指定する必要がある。

/* fizzbuzz4.rexx  FizzBuzz program for REXX, 4th edition */

fbmsg.1 = 'Fizz'
fbmsg.2 = 'Buzz'
fbmsg.3 = 'FizzBuzz'

do i = 1 to 100
  say fizzbuzz(i)
end

exit 0

fizzbuzz: procedure expose fbmsg.
  parse arg i
  fbmsg.0 = i

  idx = 0
  if i//3 = 0 then idx = idx + 1
  if i//5 = 0 then idx = idx + 2

  return fbmsg.idx

fizzbuzz内で連想配列fbmsgにアクセスするために、exposeで明示的に指定している。この方法では1つ上の階層の変数にはアクセスできるが、それよりも上の階層の変数にアクセスできるかどうかは不明だ。何となく探せば情報は見つかりそうだが、調べていない。

文字列*3の連結方法は3パターンある。

  1. 文字列を1文字以上の空白文字で区切って並べると、間にスペース1文字を挟みこんだ状態で連結される。
  2. 文字列をくっつけて並べると、間にスペースを挟みこまない状態で連結される。
  3. 演算子`||'を使用すると、間にスペースを挟みこまない状態で連結される。
/* fizzbuzz5.rexx  FizzBuzz program for REXX, 5th edition */

do i = 1 to 100
  say fizzbuzz(i)
end

exit 0

fizzbuzz: procedure
  parse arg i

  msg = ''
  if i//3 = 0 then msg = 'Fizz'
  if i//5 = 0 then msg = msg || 'Buzz'
  if msg = '' then msg = i

  return msg

2番目の方法はREXXインタプリタトークン分割する際の規則と関わってくる部分がある。なので3番目の方法を使用するほうが無難だろう。

ところで、最初にも書いたがREXXから簡単に外部ツールを呼び出し、結果を取得することができる。例によってUnix由来のseqを使うと、こんな風に書くことができる。

/* fizzbuzz6.rexx  FizzBuzz program for REXX, 6th edition */

'seq 1 100 | rxqueue'
do while queued() > 0
  parse pull line
  say fizzbuzz(line)
end

exit 0

fizzbuzz: procedure
  parse arg i

  msg = ''
  if i//3 = 0 then msg = 'Fizz'
  if i//5 = 0 then msg = msg'Buzz'
  if msg = '' then msg = i

  return msg

seqの出力をqueue経由で取得し、使用している。確実にseqを呼び出す為にコマンドを文字列で書いてある。

REXXは意外と強力だし、既存のコマンドラインツールとの親和性もそこそこ高い。何より「何でも文字列」的な発想は初心者受けしそうである。

この辺り、実は個人的にPowerShellの普及で懸念している点でもある。PowerShellの「何でもオブジェクト」な考え方にプログラマとして違和感を感じることは無いのだが、それ以外のユーザにとってはどうだろうか? 「何でも文字列」なREXXの方が易しいと感じるかもしれない。

もっともこれからREXXが普及するかというと、何かしらのブレイクスルーが無い限りあり得ないだろうと思う。

普及の妨げとなる壁も大きい。資料が少ないのがネックで、今回REXXを勉強するに当たり、たったこれだけのコードなのに色々と苦労した。それに何よりPC向けのOSで標準搭載されていない。せっかくANSI規格が存在するのに、残念なことである。

*1:元ネタは忘れたけど、確か2chだったと思う。

*2:とはいえ四則演算以外の記号は言語によってまちまちだったりするのだが。

*3:といってもREXXのデータは全て文字列なのだが……。