Fortran で三項演算子(改訂)


Fortran でコンピュータ言語でいう三項演算子を実現する方法について記します。
実用性は低いと思いますが、ギリギリの非常時や、「Fortran には三項演算子が無いくせに!」といじめられたときに「いや、なくもない」などと煙に巻くのに役立つでしょう。

改訂:

septcolor さんから MERGE 関数が (then,else,if) 並びの三項演算子として使えるというコメントをいただきました。こちらの方が ELSE が必須になりますが、スカラーを直接扱えるので便利です。また本文の例題に出した ELSE の要らない場合は SUM の MASK を使えばよいので、SUM(then,if) の形に PACK を略したほうが簡潔になります。ただ数値にしか使えず、ELSE 値として 0 が返ります。以下記事は元のままにしておきます。

前書き 三項演算子

Fortran には単項演算子と二項演算子しか存在しません。Operator Overload でもこの二つにしか対応していません。その理由は、どこかで読んだ分には、数学でもほとんど単項演算子と二項演算子しか出てこないので、それで十分であろうとのことでした。

確かに数学本を見ていても、三項演算子が出てくることはほとんどありません。理由はよく分かりませんが、三項が絡む時は関数の形にするからでしょうか?

コンピュータ言語でいう三項演算子は、三つの項を取るという点では三項ですが、if 文が式となって値を返すというもので、数学などの単項・二項演算子の延長線上にあるものではない気がします。

Fortran での実現

PACK 関数を使うと、疑似的に三項演算子相当の機能が実現できます。以下に二つの数を比較して、大きい方を返す max 関数相当の例を示します。

!k=max(i,j)
k=sum(pack([i], i>j ,[j]))

if(i>j)then
    k=i
else
    k=j
endif

ここで PACK 関数の中に、THEN 節で返す値(を配列化したもの)、IF 文の条件式、ELSE 節で返す値(を配列化したもの)の順番で並べます。この順番は C 言語の三項演算子ではなく、Python の三項演算子の順番になっています。

PACK 関数は、元々配列操作用の関数なので、配列構成子 [...] によって、1 要素の配列にしています。これをスカラーに戻す方法がないので、sum 関数でナンセンスな計算をやってスカラーに戻しています。なお条件式はスカラーのままでも、全配列操作として全要素に単一の論理値が作用するから大丈夫です。配列構成子 [...] でくくって、論理型の配列にしても良いです。

PACK 関数

文法事項。任意型の配列から、論理型の mask が .true. である要素を取り出す。mask が .false. である所を埋めたい場合、optional な vector 配列を与えておくと対応要素をで埋めてくれる。 

result = PACK (array,mask[,vector])

array
(Input) Must be an array. It may be of any data type.

mask
(Input) Must be of type logical and conformable with array. It determines which elements are taken from array.

vector
(Input; optional) Must be a rank-one array with the same type and type parameters as array. Its size must be at least t, where t is the number of true elements in mask. If mask is a scalar with value true, vector must have at least as many elements as there are in array.

Elements in vector are used to fill out the result array if there are not enough elements selected by mask.

PACK 関数は、本来は array processor 型の SIMD 並列コンピュータ向けの機能だと思います。SIMD 型のプロセッサでは複数の配列要素が一括で実行されるので、( else 節の無い)if 文の条件分けは、実行するけど結果を捨てるという mask で実現されます。しかしこの場合、捨てる要素が多くなると SIMD の利点が生かされなくなるので、そういう場合はあらかじめ PACK 関数で実行処理されるべき配列要素を抜き出しておき、SIMD 処理した後、UNPACK 関数で配列の元の位置に戻すことが考えられていたのではないかと思います。

下図は 1960 年代の夢の SIMD 型並列スパコン ILLIAC IV のパンフレットから借りてきた参考イラストです。(このイラストでは計算するけど結果を捨てるのではなく、計算せずに寝てるようですが。)

利用例

三項演算子は、値を返す if 文と見なせるので、値しか取れない所で条件分けをしたいときなどに利用できます。たとえば、WRITE 文などで使えます。ここで WRITE 文の場合、配列とスカラーの差が見えないので、配列をスカラーに直すための余分な sum 関数を省略できてスッキリします。

以下の例では(else 節の無い場合ですが)、20 までの整数の約数を求めて列挙しています。

program divisor
    implicit none
    integer:: i,j
    do i=1,20
        print'(*(g0,x))',(pack([j], mod(i,j)==0), j=1,i)
    enddo
end program divisor
1
1 2
1 3
1 2 4
1 5
1 2 3 6
1 7
1 2 4 8
1 3 9
1 2 5 10
1 11
1 2 3 4 6 12
1 13
1 2 7 14
1 3 5 15
1 2 4 8 16
1 17
1 2 3 6 9 18
1 19
1 2 4 5 10 20