匿名希望のおでんFortranツヴァイさん太郎

生き物、Fortran、川について書く

読書感想文 : A geometric approach to differential forms. Second Edition.

本稿は以下の書籍の感想文です。

  • Bachman, David (2011) A geometric approach to differential forms. Second Edition. Springer. pp.156. *1

本書の特色は表題の通り、微分形式をできる限り幾何学的直観に基づいて説明することです。 微分形式はしばしば外積代数から導入されたり、テンソル積から構成されたりなど 代数的に導入されることが多く、一体何をしているのかが分りにくいです。 外微分についても形式的な定義にとどまることが多々あります。 結果的に、計算はできるし定理の証明も追えるが、微分形式自体が何を表しているのか さっぱりわからないという事態に陥ります。

それに対して、本書はできる限り微分形式を直観的に理解させようと試みます。 図も豊富に用いられます。 このような方針で微分形式を解説する本はあまり多くないのではないでしょうか。 外微分については何を微分しているのか、なぜ k-形式を外微分すると (k+1)-形式になるのかを説明しています。 微分形式の積分は 2-形式の積分から導入することで、定義の妥当性を直観的に理解させてくれます。 積分の手順はこれでもかというくらい丁寧に繰り返し解説されます。

本書のもう一つの特色は、きわめて簡単な練習問題が数多く設けられていることです。 難度としては、定義が分かれば解ける、あるいは例を見れば同じように解けるくらいのものが多いです。 曲面を描画させる問題もあります。 このおかげで計算の感覚や幾何学的感覚を磨くことができます。 曲線あるいは曲面のパラメータ付けの問題もたくさんあり、 これらを解くことで後半の多様体の話もすんなり頭に入ってくる仕組みになっています。

私は本書を読んだ後もまだ微分形式についてわからない点がありますが、 理解のきっかけを多く得ることができました。 ホッジの星印作用素の直観的説明がないのは物足りないですが、 書物全体の流れを考慮すると欠点とは言えません。 注意点としては、代数的説明から入った場合よりも計算規則の説明が煩雑に感じる可能性があることです。 そのため、微分形式入門の最初の一冊として読むよりも、微分形式の計算や代数的構成を経験した後に、 個々の概念の繋がりを理解するために読む方が得るものが多いと思います。 微分形式で何をやっているかわからず困っている方は是非読んでみてください。

sample と specimen の違い

本稿では sample と specimen の違いを考察します。 単語の意味は Oxford Advanced Learner's Dictionary*1で調べました。

sample の意味は以下の通りです。

sample

  • a number of people or things taken from a larger group and used in tests to provide information about the group
  • a small amount of a substance taken from a larger amount and tested in order to obtain information about the substance
  • a small amount or example of something that can be looked at or tried to see what it is like
  • a piece of recorded music or sound that is used in a new piece of music

sample はある集団についての情報を知るために取り出した少数のもの、というのが基本的な意味であると考えます。4 番目の意味は、集団の特性を知るという意味は失われていますが、ある集団から取り出した少数のもの、という意味は保持されています。

specimen の意味は以下の通りです。

specimen

  • a small amount of something that shows what the rest of it is like (SYNONYM sample)
  • a single example of something, especially an animal or a plant
  • a small quantity of blood, urine, etc. that is taken from somebody and tested by a doctor

specimen も集団の特性を知るためのに取り出した少数のもの、という意味があります。特に、1 番目の意味は類義語として sample が挙げられています。しかしながら、2 番目の意味は sample と少し異なり、あるものに対する単一の例となっています。

sample と specimen を比べると、sample の方が集団を知るために取り出したもの、という意味が前面に出ているように思います。例えば、specimen の 2 番目の意味はあるものに対する単一の例ですから、「集団の特性を知るために」という部分が sample に比べて弱くなっています。

specimen の 3 番目の意味を考慮すると、文脈によって sample になるか specimen になるかが変わる場合もあるかもしれません。例えば、人の血液を集めた場合、ある人の健康状態を判定するのに使う場合は specimen になり、ヒトの血液一般について何かを統計的に調べる材料にする場合は sample になるのではないかと思います。ただし、この辺りの使い分けは業界の慣習によるかもしれません。

間違い等あればご指摘いただけますと幸いです。

コマンド ライン引数の個数で処理を変えるプログラムを作る

本稿では、コマンド ライン引数の個数で処理を変えるプログラムを Fortran で作ります。

作成するプログラムは以下のように動作します。

  • コマンド ライン引数で自然数のみを与えた場合
  • コマンド ライン引数で自然数とファイル名を与えた場合
    • 指定のファイルに、与えた自然数以下の素数を出力します。

処理の流れは以下の通りです。

  1. コマンド ライン引数を解析する。
  2. 与えられた自然数以下の素数を取得する。
  3. 素数を出力する。

従って、概念上必要なものは、コマンド ライン引数を解析するクラス、素数を判定するクラス、素数を出力するクラスです。
今回はコマンド ライン引数の解析、素数の出力は大きな処理ではないので、クラスを作らずに対応します*1
素数を判定するために class_sieve_of_eratosthenes を作ります。
コマンド ライン引数の取得には以下の記事で作った class_command_getter を用います。
y-tis.hatenablog.com

自然数のみを与えて実行すると以下のように出力されます。

sieve_of_eratosthenes.exe 10
 Prime numbers are followings:
           2
           3
           5
           7
The number of primes is 4

以下のように、自然数とファイル名を与えて実行すると、テキスト ファイルに出力されます。

sieve_of_eratosthenes.exe 10 primes.txt

primes.txt には、以下のように出力されます。

           2
           3
           5
           7

メイン プログラムは以下の通りです。コマンド ライン引数の型は解析していないので、第一引数で文字列を指定してしまうとエラーになる上、使用例を表示せずに終了します。また、大きすぎる値を指定するとエラーになります。この辺りの処理は今後の課題です。

! 指定の自然数以下の素数を調べるプログラムです。
!
! コマンド ライン引数で自然数のみを指定すると、
! 指定した自然数以下の素数とその個数を標準出力に表示します。
! 
! コマンド ライン引数で自然数とファイル名を指定すると、
! 指定した自然数以下の素数を、指定のファイルに出力します。
program main_program
    use, intrinsic :: iso_fortran_env
    use class_command_getter
    use class_sieve_of_eratosthenes
    implicit none

    ! 走査する自然数の上限 
    integer upper_limit
    ! 出力ファイル名
    character(:), allocatable :: output_file_name
    ! コマンド ライン引数取得のためのクラス
    type(command_getter) args_getter
    ! エラトステネスのふるいを実行するクラス
    type(sieve_of_eratosthenes) sieve
    ! 素数を格納する配列
    integer(int32), allocatable :: prime_numbers(:)


    ! コマンド ライン引数が不正であれば終了します。
    if (args_getter%get_size() == 0 .or. args_getter%get_size() > 2) then
        write(error_unit, *) "ERROR:"
        write(error_unit, *) "The command line arguments are followings:"
        write(error_unit, *) " - positive_integer: integer"
        write(error_unit, *) " - output_file_name [optional]: character"
        write(error_unit, *) ""
        write(error_unit, *) "(e.g.1) sieve_of_eratosthenes.exe 100"
        write(error_unit, *) " => Prime Numbers are written in standard output."
        write(error_unit, *) ""
        write(error_unit, *) "(e.g.2) sieve_of_eratosthenes.exe 100 prime_numbers.txt"
        write(error_unit, *) " => Prime Numbers are written in 'prime_numbers.txt'."
        write(error_unit, *) ""
        write(error_unit, *) "Program ended."
        stop
    end if

    ! 指定された自然数が 0 以下であれば、エラーを表示して終了します。
    if (args_getter%to_int32(1) <= 0) then
        write(error_unit, *) "ERROR : The first argument must be a positive integer."
        write(error_unit, *) "Program ended."
        stop
    end if

    ! 出力ファイル名が指定されている場合
    if (args_getter%get_size() == 2) then
        ! 出力ファイル名を決定します。
        output_file_name = args_getter%get(2)
    else
        ! 出力ファイル名は空にします。
        output_file_name = ""
    end if

    ! コマンド ライン引数から走査する自然数の上限を決定します。
    upper_limit = args_getter%to_int32(1)

    ! エラトステネスのふるいを実行し、素数を格納した配列を取得します。
    prime_numbers = sieve%sieve(upper_limit)

    if (output_file_name == "") then
        ! 出力ファイル名の指定がない場合
        ! 素数の個数と素数の一覧を標準出力に表示します。
        call show_prime_numbers(prime_numbers)
    else
        ! 出力ファイル名が指定されている場合
        ! 素数を指定の名前の出力ファイルに出力します。
        call write_prime_numbers(prime_numbers, output_file_name)
    end if


    contains


    ! 与えられた整数配列の個数と要素を標準出力に表示します。
    !
    ! @param prime_numbers 整数配列
    subroutine show_prime_numbers(prime_numbers)
        ! 引数
        integer(int32), intent(in) :: prime_numbers(:)
        ! 素数の個数
        integer(int32) number_of_primes

        ! ループ カウンタ
        integer(int32) i

        ! 素数の個数を取得します。
        number_of_primes = size(prime_numbers)

        ! 素数の個数が 0 個の時は終了します。
        if (number_of_primes == 0) return

        ! 素数を標準出力に列挙します。
        print*, "Prime numbers are followings:"
        do i = 1, size(prime_numbers)
            print*, prime_numbers(i)
        end do

        ! 素数の個数を表示します。
        print'(A, I0)', "The number of primes is ", number_of_primes
    end subroutine


    ! 与えられた整数配列を指定のファイルに出力します。
    !
    ! @param prime_numbers 整数配列
    ! @param output_file_name 出力ファイル名
    subroutine write_prime_numbers(prime_numbers, output_file_name)
        ! 引数
        integer(int32), intent(in) :: prime_numbers(:)
        character(*), intent(in) :: output_file_name
        ! 装置番号
        integer(int32) output_file_unit
        ! ループ カウンタ
        integer(int32) i

        open(newunit=output_file_unit, file=output_file_name, status="replace")

        if (size(prime_numbers) == 0) then
            ! 素数の個数が 0 個の時は終了します。
            return
        else
            ! 素数を出力ファイルに書き出します。
            do i = 1, size(prime_numbers)
                write(output_file_unit, *) prime_numbers(i)
            end do
        end if

        close(output_file_unit)
    end subroutine
end program

class_command_getter は以下の通りです。

! コマンド ライン引数を取得するクラスです。
module class_command_getter
    use, intrinsic :: iso_fortran_env
    implicit none
    
    private
    
    ! コマンド ライン引数を表す構造体です。
    type, private :: command_line_argument
        character(:), private, allocatable :: value
    end type

    
    ! コマンド ライン引数を取得するクラスです。
    type, public :: command_getter
        ! コマンド ライン引数を取得していれば true、そうでなければ false
        logical, private :: has_values = .false.
        ! コマンド ライン引数
        type(command_line_argument), private, allocatable :: value(:)

        contains

        procedure, private, pass :: get_all_command_line_argument
        procedure, public, pass :: get
        procedure, public, pass :: get_size
        procedure, public, pass :: to_int32
        procedure, public, pass :: to_real64
    end type


    contains


    ! i 番目のコマンド ライン引数を返します。戻り値の型は character です。
    !
    ! @param i コマンド ライン引数の順番 (1 始まり、int32)
    ! @return i 番目のコマンド ライン引数
    function get(this, i) result(arg)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        character(:), allocatable :: arg

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        arg = this%value(i)%value
    end function


    ! コマンド ライン引数の個数を返します。
    !
    ! @return コマンド ライン引数の個数
    function get_size(this) result(number_of_args)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! 戻り値
        integer(int32) number_of_args

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        number_of_args = size(this%value)
    end function


    ! 指定されているコマンド ライン引数を全て取得します。
    subroutine get_all_command_line_argument(this)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! コマンド ライン引数
        character(:), allocatable :: arg
        
        ! コマンド ライン引数の個数
        integer(int32) number_of_args
        ! コマンド ライン引数の文字列長を格納する配列
        integer(int32) length
        ! コマンド ライン引数取得時の状態
        integer(int32) status
        ! ループ カウンタ
        integer(int32) i
        
        ! コマンド ライン引数の個数を取得します。
        number_of_args = command_argument_count()
        
        ! コマンド ライン引数を格納する配列を生成します。
        allocate(this%value(number_of_args))
        
        do i = 1, number_of_args
            ! i 番目のコマンド ライン引数の長さを取得します。
            call get_command_argument(i, length=length, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if
            
            ! i 番目のコマンド ライン引数を取得します。
            allocate(character(length) :: arg)
            call get_command_argument(i, arg, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if

            ! コマンド ライン引数を格納します。
            this%value(i)%value = arg
            
            deallocate(arg)
        end do

        this%has_values = .true.
    end subroutine


    ! i 番目のコマンド ライン引数を int32 に変換します。
    !
    ! @param i int32 に変換したいコマンド ライン引数のインデックス
    ! @return int32 の整数
    function to_int32(this, i) result(output_integer)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        integer(int32) output_integer
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を int32 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_integer

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_int32 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function


    ! i 番目のコマンド ライン引数を real64 に変換します。
    !
    ! @param i real64 に変換したいコマンド ライン引数のインデックス
    ! @return real64 の実数
    function to_real64(this, i) result(output_real_value)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        real(real64) output_real_value
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を real64 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_real_value

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_real64 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function
end module

class_sieve_of_eratosthenes は以下の通りです。

! エラトステネスのふるいを実行するクラスです。
module class_sieve_of_eratosthenes
    use, intrinsic :: iso_fortran_env
    implicit none

    private

    ! エラトステネスのふるいを実行するクラスです。
    type, public :: sieve_of_eratosthenes

        contains

        procedure, public, pass :: sieve
    end type


    contains


    ! 指定の自然数以下の素数を格納した int32 配列を返します。
    ! 
    ! @param upper_limit 走査する自然数の上限
    ! @return 指定の数以下の素数を格納した int32 配列
    function sieve(this, upper_limit) result(prime_number_array)
        ! 引数
        class(sieve_of_eratosthenes), intent(in) :: this
        integer(int32), intent(in) :: upper_limit
        ! 戻り値
        integer(int32), allocatable :: prime_number_array(:)
        ! ループ カウンタ
        integer(int32) i

        ! upper_limit が 0 以下であればエラーを表示し、大きさ 0 の配列を返します。
        if (upper_limit <= 0) then
            write(error_unit, *) "ERROR : upper_limit must be positive."
            write(error_unit, *) "An empty int32 array is returned."
            allocate(prime_number_array(0))
            return
        end if

        ! 素数を配列に格納します。
        prime_number_array = pack([(i, i = 1, upper_limit)], mask = is_prime_number(upper_limit))
    end function


    ! インデックス i が素数なら要素が true である論理型配列を初期化します。
    !
    ! @param array_size 論理型配列の大きさ
    ! @return インデックス i が素数なら true である論理型配列
    function initialize_prime_number_flag(array_size) result(is_prime_number)
        ! 引数
        integer(int32), intent(in) :: array_size
        ! 戻り値
        logical, allocatable :: is_prime_number(:)

        ! input_number 以下の整数が素数であるかを判定する論理型配列を生成します。
        allocate(is_prime_number(array_size), source = .true.)
        ! 1 を素数から外します。
        is_prime_number(1) = .false.

        ! array_size が 1 であれば終了します。
        if (array_size == 1) return

        ! 2 を素数と判定します。
        is_prime_number(2) = .true.

        ! array_size が 2 であれば終了します。
        if (array_size == 2) return

        ! array_size が 4 以上の場合
        if (array_size >= 4) then
            ! 2 より大きい偶数を素数でないと判定します。
            is_prime_number(4:array_size:2) = .false.
        end if
    end function


    ! インデックス i が素数なら要素が true である、指定の大きさの論理型配列を返します。
    !
    ! @param array_size 走査する自然数の上限
    ! @return インデックス i が素数なら要素 true である、大きさ array_size の論理型配列
    function is_prime_number(array_size) result(is_prime)
        ! 引数
        integer(int32), intent(in) :: array_size
        ! インデックス i が素数なら true、そうでないなら false を格納する logical 配列
        logical, allocatable :: is_prime(:)
        ! 実際に走査する自然数の最大値
        integer(int32) max_iteration
        ! ループ カウンタ
        integer(int32) i

        ! インデックス i が素数なら要素が true である論理型配列を初期化します。
        ! 2 を素数、2 より大きい偶数を非素数と判定します。
        is_prime = initialize_prime_number_flag(array_size)

        ! 3 から sqrt(array_size) までの奇数が素数であるか走査します。
        ! 素数 p の倍数を消去する時、p * 2 から p * (p - 1) は
        ! すでに消去されているので p * p から消去します。
        ! 従って、 sqrt(array_size) までループを回せば十分です。
        max_iteration = int(sqrt(dble(array_size)))
        do i = 3, max_iteration, 2
            ! すでに判定した整数はとばします。
            if (.not. is_prime(i)) cycle

            ! i^2 から array_size までの i の倍数を false にします。
            is_prime(i * i:array_size:i) = .false.
        end do
    end function
end module

*1:処理の大きさに関わらず、クラスを作っておく方が後々プログラムを変更しやすいで、本当は作っておくほうが良いです。

コマンド ライン引数を用いる利点と Fortran における使用例

本稿にはコマンド ライン引数を用いることの利点、 ならびに Fortran におけるコマンド ライン引数使用の例を示しています。

私が考えるコマンド ライン引数を利用する利点は以下の通りです。

  • 使いまわしやすいプログラムを書ける
  • バッチ処理が可能になる

入力ファイル、出力ファイル、パラメータなどを替えて同じプログラムを走らせたいとき、これらの値をハード コーディングしていると、毎回違う値を入力してコンパイルし直す必要があります。しかし、これらをコマンド ライン引数として与えるプログラムにしていれば、コンパイルし直すことなく使用することができます。

バッチ ファイルにまとめて書くことで、複数の処理をまとめて実行することもできます。例えば、Fortran で計算し、Python で可視化する、という処理を一括で行えます。 何を入力したかをバッチ ファイルに残すことができるので、計算を再現するための条件を残しておくこともできます。また、バッチ ファイル自体がプログラムの使用例になります。

Fortran に限らず、コマンド ライン引数を用いたプログラムにおける処理の基本的な流れは以下の通りです。

  1. コマンド ライン引数を受け取ったら、構文の解析を行う。

    • 引数の個数、値の範囲、型などが正しいか分析します。
  2. 構文にエラーがあれば、ユーザーに使用方法を通知する。

    • 例外を投げるだけだと、ユーザーには意味不明の文が流れるので、 きちんと使用方法を提示します。
  3. 意味がわかる名前の変数に、受け取った引数の値をすぐに代入する。

    • args(1)、args(2)... のような名前のまま使うと、 何を格納した変数かわからないためです。
    • スコープ最小の原則にそむくように見えますが、 スコープ最小の原則の目的は覚えておくべきことを最小にすることです。 すぐに意味のある変数に代入することで、args(1)、args(2)... が何であるかを 覚えておく必要がなくなります。

コマンド ライン引数を Fortran で用いるには get_command_argument*1 を用います。 get_command_argument はそのままでは使いにくいので、command_getter クラスを作りました。

この command_getter を用いたプログラムの例を示します。

メイン プログラムは以下の通りです*2

! コマンド ライン引数取得のテストです。
! 名前、年齢、体重をコマンド ライン引数で与えます。
! 標準出力にコマンド ライン引数で与えた名前、年齢、体重を表示します。
program main_program
    use, intrinsic :: iso_fortran_env
    use class_command_getter
    implicit none

    ! 名前
    character(:), allocatable :: name
    ! 年齢
    integer(int32) age
    ! 体重
    real(real64) weight

    ! コマンド ライン引数を取得するクラス
    type(command_getter) args_getter


    ! コマンド ライン引数を解析します。
    if(args_getter%get_size() /= 3) then
        ! エラーを通知してプログラムを終了します。
        write(error_unit, *) "There must be following command line arguments."
        write(error_unit, *) "  - name: character"
        write(error_unit, *) "  - age: integer"
        write(error_unit, *) "  - weight (kg): real number or integer"
        write(error_unit, *) "(e.g.) command_getter_test.exe Rikishi 25 150.6"
        write(error_unit, *) "Program ended."
        stop
    end if
    
    ! 名前を決定します。
    name = args_getter%get(1)
    ! 年齢を決定します。
    age = args_getter%to_int32(2)
    ! 体重を決定します。
    weight = args_getter%to_real64(3)
    
    ! 名前、年齢、体重を標準出力に表示します。
    print'(A, A)', "Name: ", name
    print'(A, I3)', "Age: ", age
    print'(A, F5.1, A)', "Weight: ", weight, " kg"
end program

class_command_getter は以下の通りです。

! コマンド ライン引数を取得するクラスです。
module class_command_getter
    use, intrinsic :: iso_fortran_env
    implicit none
    
    private
    
    ! コマンド ライン引数を表す構造体です。
    type, private :: command_line_argument
        character(:), private, allocatable :: value
    end type

    
    ! コマンド ライン引数を取得するクラスです。
    type, public :: command_getter
        ! コマンド ライン引数を取得していれば true、そうでなければ false
        logical, private :: has_values = .false.
        ! コマンド ライン引数
        type(command_line_argument), private, allocatable :: value(:)

        contains

        procedure, private, pass :: get_all_command_line_argument
        procedure, public, pass :: get
        procedure, public, pass :: get_size
        procedure, public, pass :: to_int32
        procedure, public, pass :: to_real64
    end type


    contains


    ! i 番目のコマンド ライン引数を返します。戻り値の型は character です。
    !
    ! @param i コマンド ライン引数の順番 (1 始まり、int32)
    ! @return i 番目のコマンド ライン引数
    function get(this, i) result(arg)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        character(:), allocatable :: arg

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        arg = this%value(i)%value
    end function


    ! コマンド ライン引数の個数を返します。
    !
    ! @return コマンド ライン引数の個数
    function get_size(this) result(number_of_args)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! 戻り値
        integer(int32) number_of_args

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        number_of_args = size(this%value)
    end function


    ! 指定されているコマンド ライン引数を全て取得します。
    subroutine get_all_command_line_argument(this)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! コマンド ライン引数
        character(:), allocatable :: arg
        
        ! コマンド ライン引数の個数
        integer(int32) number_of_args
        ! コマンド ライン引数の文字列長を格納する配列
        integer(int32) length
        ! コマンド ライン引数取得時の状態
        integer(int32) status
        ! ループ カウンタ
        integer(int32) i
        
        ! コマンド ライン引数の個数を取得します。
        number_of_args = command_argument_count()
        
        ! コマンド ライン引数を格納する配列を生成します。
        allocate(this%value(number_of_args))
        
        do i = 1, number_of_args
            ! i 番目のコマンド ライン引数の長さを取得します。
            call get_command_argument(i, length=length, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if
            
            ! i 番目のコマンド ライン引数を取得します。
            allocate(character(length) :: arg)
            call get_command_argument(i, arg, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if

            ! コマンド ライン引数を格納します。
            this%value(i)%value = arg
            
            deallocate(arg)
        end do

        this%has_values = .true.
    end subroutine


    ! i 番目のコマンド ライン引数を int32 に変換します。
    !
    ! @param i int32 に変換したいコマンド ライン引数のインデックス
    ! @return int32 の整数
    function to_int32(this, i) result(output_integer)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        integer(int32) output_integer
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を int32 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_integer

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_int32 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function


    ! i 番目のコマンド ライン引数を real64 に変換します。
    !
    ! @param i real64 に変換したいコマンド ライン引数のインデックス
    ! @return real64 の実数
    function to_real64(this, i) result(output_real_value)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        real(real64) output_real_value
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を real64 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_real_value

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_real64 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function
end module

*1:https://gcc.gnu.org/onlinedocs/gfortran/GET_005fCOMMAND_005fARGUMENT.html

*2:型チェックの良い方法が思いつかなかったので、今回はコマンド ライン引数の数だけを解析しています。

読書感想文 : 読書と社会科学

本稿は以下の書籍の感想をまとめたものです。

  • 内田義彦 (1985) 読書と社会科学. 岩波新書. pp213.

本書は社会科学を学ぶ方法としての読書を論じた本です。 著者は情報としての読みと古典としての読みを区別します。 情報としての読みとは、文字通り情報として受け取るだけの読みです。 古典としての読みとは、情報の受け取り方を変え、ものの考え方を変えるような読みです。 本書では古典としての読みを重視します。

古典としての読みは、文字通り古典を対象とします。 古典とは、一読明快を目指して書かれたにも関わらず、一読明快からはみ出す内容を含むものです。 丁寧に読みこむほど、読まれた内容が個性的になります。 一般に古典と呼ばれていない本も「私にとっての古典」になりえます。

古典としての読みの目的は概念装置を獲得することです。 概念装置の獲得には、装置を一から開発するのと全く同じ種類の自主性と労苦が必要です。 これを指して著者は自前の概念装置という表現を用います。

一般に、本には学説構成に必要な全ての概念装置が登場しません。 個々の概念装置の基礎になる概念装置が本の中に登場しないことが多いのです。 この基礎の概念装置を著者は大枠の概念装置と呼びます。 大枠の概念装置を持っていない状態とは、本で構成される学説の基礎が わからない状態ですから、本の内容を理解できません。 また、時代や状況が違う中で生まれた概念装置は、 そのまま現代に役立つとは限らないことに注意が必要です。 従って、概念装置を作る営みそのものを読書を通じて吸収する必要があります。

古典としての読みは、本を仮説的に信じることから始まります。 二種類の信があります。 著者が間違えるわけがないという信と、自分の読みが正しいという信です。 二つの信に基づいて読む内に、書かれた内容に対する明確な疑問が生じます。 その疑問を解くための探索によって、対象の本に対する理解を深めます。

以上が古典としての読みのまとめです。

自前の概念装置は非常に府に落ちる表現です。

数学の学習も自前の概念装置の獲得の過程だと私は考えます。 その定理を成立させる条件や定理の証明を追うだけでは、 定理を理解できないことがしばしばあります。 自分で例や反例を作ったり、一から自分で理論を構成することで、 どのような概念が必要なのか理解できるようになります。 定理を成立させるために何を定義しなければならないかがわかってきます。 自分にとって自然な文脈で理論を構成できるようになると、 諸概念が非常に当たり前のように感じられます。

このように考えると、自前の概念装置とは実体験に引き寄せた解釈のこととも言えます。 私の場合、河川の散策、遊び、調査の経験が、文献の理解や解釈に 役立ったことが幾度となくあります。 経験がなかったころは、文献から得た知識があっても、 実河川で起こっていることに対して仮説を立てることができませんでした。

著者は次のように言います。

ウェーバーについて詳しく知ったって、ウェーバーのように考える考え方、 なるほどさすがにウェーバーを長年読んできた人だけあってよく見えるものだなあ、 ウェーバー学も悪くないと思わせる見方を身につけなければ仕方がない。(p3)

 

新奇な情報は得られなくても、古くから知っていたはずのことがにわかに新鮮な風景 として身を囲み、せまってくる、というような「読み」があるわけです。(p13)

 

本を読むことは大事ですが、自分を捨ててよりかかるべき結論を求めて本を読んじゃ いけない。本を読むことで、認識の手段としての概念装置を獲得する。 これがかなめです。(p157)

どのような態度で本に挑み、何を学ぶべきか、それを本書は考えさせてくれます。 読書を通じた学問について考える時は、本書を古典として読むことをおすすめします。

remove、eliminate、exclude の違い

ある集団から特定のものを取り除くことを英語で表現するために、 どんな単語を使えばよいか困ってしまいました。 ひとまず思いついた remove、eliminate、exclude について違いを調べました。

Oxford Learner's Dictionary *1 で定義を調べました。 以下、語の意味を感覚的につかむため、その語の複数の定義を並べます。 定義文中で使用される単語の内、語の理解にあたって重要そうな単語も併記します。

remove

  • to take something/somebody away from a place
  • to take off clothing, etc. from the body
  • to get rid of something unpleasant, dirty, etc.; to make something disappear
  • to dismiss somebody from their position or job

get rid of

  • to make yourself free of somebody/something that is annoying you or that you do not want; to throw something away

annoy

  • to make somebody slightly angry
  • to make somebody uncomfortable or unable to relax

dismiss

  • to decide that somebody/something is not important and not worth thinking or talking about
  • to put thoughts or feelings out of your mind
  • to officially remove somebody from their job

ある場所からものをどけることから、人を辞めさせることまで、広い意味で何かを取り除くことを示しています。

eliminate

  • to remove or get rid of something/somebody
  • to defeat a person or a team so that they no longer take part in a competition, etc.
  • to kill somebody, especially an enemy or opponent

1 番目の定義文から、remove とある程度同じように見えます。 2、3 番目のように、「敵を倒す」ような意味での「取り除く」でもあるようです。 2 番目の定義文には no longer take part in とあるので、かなり強い意味だと思われます。

exclude

  • to deliberately not include something in what you are doing or considering
  • to prevent somebody/something from entering a place or taking part in something
  • to decide that something is not possible

deliberately

  • done in a way that was planned, not by chance
  • slowly and carefully

1 番目の定義から、exclude は eliminate や remove と視点が違うことがわかります。 eliminate も remove も何かを取り除くことを意味しますが、 exclude は何かを含めないこと、何かが入らないようにすることを意味します。

上記を踏まえると、冒頭の「ある集団から特定のものを取り除くこと」を表現する時は、以下のような使い分けになると考えました。

  • すでに集団になっているところから取り除くことを表現する場合は remove を用いる。
    • 例えば、「リスト A の中から i 番目の要素を除去する」という表現をする場合。
  • remove を使う場面で、取り除いたら二度と戻らないような強い意味を持たせる場合は eliminate を用いる。
  • 特定の対象を含まないようにすることを表現する場合には exclude を用いる。
    • 例えば、「条件 A を満たすデータは含まずに解析した」という表現をする場合。
      • 「観測データの全部から条件 A を満たすデータを除いた」という表現なら remove でもいいかもしれない。

あまり自信がないので、間違いがあればご教示いただけると幸いです。

鉛筆と万年筆

私はノートに書きながら考えをまとめることが多いです。 筆記具は鉛筆と万年筆を使っており、その時々の気分でどちらを使うかを決めます。

万年筆のインクは何種類かありますが、目的と気分で使い分けます。 例えば、モンブランのロイヤルブルーのような水で簡単に流れるインクで、 消えて困るような内容は書きません。

鉛筆と万年筆のどちらを使うかは気分によって決めますが、 ある程度考えがまとまっている内容を文章にする時は万年筆を、 言葉や絵が浮かばないような段階の思索では鉛筆を取ることが多いです。

万年筆はなめらかに筆が走り、疲れにくいという特徴があります。 鉛筆は文も図も荒々しく殴り書くことができ、万年筆に比べて抵抗のある書き味です。 このような生理的な感覚の違いが、どちらを使いたくなるかに関わっているように思います。

万年筆はプラチナ万年筆のプレジールとモンブランの No.34 を使っています。 インクはプラチナ万年筆のブルーブラック、モンブランのロイヤルブルー、 鉛筆は三菱鉛筆の Hi-uni 4B、 紙は大学生協の B5 のフールス紙ノートとコクヨ野帳 (sketch book) がお気に入りです。 No.34 とモンブランのロイヤルブルーの相性は抜群で、 他の筆記具とは一線を画す気持ちのいい書き味です。

本に書き込むには鉛筆は太いので、代わりにプラチナ万年筆のプレスマンを使います。 芯は 0.9 mm であり書き込みに使える位には細い一方で滑らかさがあり、 本体も軽いため鉛筆に近い書き味です。

色々なものがデジタル化して便利になっています。 同じように、筆記具と紙も洗練されて残ってほしいと思います。

bungu.univcoop.or.jp