LoginSignup
10
3

More than 1 year has passed since last update.

Fortranでユーザー定義演算子.in.を作る

Last updated at Posted at 2022-12-25

Objective and Motivation

ある整数/浮動小数点数/文字列が何らかの集合の要素であるかを調べたいときがよくありますよね.
そのときに,if文を使って


my_value_is_in_my_list=.false.
do i=1,number_of_element
    if(my_list(i)==my_value)then
        my_value_is_in_my_list=.true.
    endif 
end do

のように条件分岐するのが常道ですが,これは少なくとも3つのバグの温床となります.

  1. イテレータをイテレーション中に誤って変更してしまう
  2. 条件文を書き忘れる,ないし書き誤る
  3. 戻り値の初期化を忘れる

さらに,集合の側をどう定義するかも問題で,有限個であれば列挙したリストを作成しておくのもよいですが,任意の開集合や閉集合について判定したいときなどはまた書き方を変える必要があります.
こうした苦労なしに,

print *, my_value .in. [10,11,12,12]
print *, my_value .in. [-1.0d0,2.0d0,3.0d0]
print *, my_value .in. to_range(20,30)!暗黙的に,範囲は閉区間
!以下,閉区間/開区間を明示
print *, my_value .in. to_range('[',20,30,']')!閉区間
print *, my_value .in. to_range('(',20,30,')')!開区間
print *, my_value .in. to_range('(',20,30,']')!半開区間

! 文字列
print *, my_value .in. "ABCDEFG"

のように呼び出せたらいいなと思いました.
実装方針としては,

  1. Fortranの組み込み関数rangeとの衝突を避ける.
  2. type(Range_)を定義しておいて,そこで最小値と最大値をメンバ変数に持たせておく
  3. interface文を使い,演算子オーバーロードにより値がinteger/real/characterのいずれであっても適当な関数を呼び出せるようにする.

以下,実装の一部です.

module RangeClass
    type :: Range_
        real(real64) :: x_range(1:2)=[0.0d0 , 0.0d0]
    contains 



    interface to_range
        module procedure :: to_range_int32, to_range_real64
    end interface

    interface operator(.in.)
        module procedure :: in_detect_by_range_int32,in_detect_by_range_real64, 
    end interface



function in_detect_by_range_int32(intval,in_range) result(ret)
    integer(int32),intent(in) :: intval
    type(Range_),intent(in) :: in_range
    logical :: ret

    ret = (in_range%x_range(1) <= dble(intval) ) .and. (dble(intval) <= in_range%x_range(2)  )

end function


function in_detect_by_range_real64(intval,in_range) result(ret)
    real(real64),intent(in) :: intval
    type(Range_),intent(in) :: in_range
    logical :: ret

    ret = (in_range%x_range(1) <= intval ) .and. (intval <= in_range%x_range(2)  )

end function



function to_range_int32(from,to) result(ret_range)
    type(Range_) :: ret_range
    integer(int32),intent(in) :: from,to

    ret_range%x_range(1:2) = dble([from,to])

end function


function to_range_real64(from,to) result(ret_range)
    type(Range_) :: ret_range
    real(real64),intent(in) :: from,to

    ret_range%x_range(1:2) = [from,to]
    
end function

...

end module RangeClass

実際に使ってみます.

program main
    use RangeClass
    implicit none

    print *, 300 .in. to_range(200,400) ! True

end program

補遺

.in.の設計についてベストプラクティスを模索中です.
type::range_を定義しておいて,to_range().in.に渡すことで,ユーザーにtype::range_の存在を意識させないのはよいかと思いますが,
to_range()引数がwordyな気もします.

このような.in.の設計がよいのでは,というコメントを歓迎します.この記事がきっかけとなり議論が深まることを期待しています.

また,実装は今しがた着手して上記機能のみ実装したのですが,まだ全てを実装できていません.すみません.
characterのところまで実装できましたら追記します.

10
3
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3