Fortranでもコマンドライン引数をリストで得たい．

# 背景
コマンドライン引数を得る方法として，Fortranでは```command_argument_count()```関数で引数の数を取得した上で```get_command_argument()```サブルーチンで特定のコマンドライン引数値を得ることができます．この方法はシンプルでよいですが，コマンドライン引数一覧をオブジェクトとして保持することはできず，特定のコマンドライン引数を得るためには毎回これらの手続を呼ぶ必要があります．

一方で，Pythonでは，```sys.argv()```関数によりコマンドライン引数一覧をリストとして得られます．そのため，コマンドライン引数一覧を頻繁に参照する場合にはより便利です．

そこで，Fortranでもコマンドライン引数をリストとして得るべく，実装しました．


# リストクラスの作成
複数のデータ要素をもつ1次元配列はリストと呼ばれます．リストの各要素には1つのインデックスが対応しており，インデックスを参照することで要素を受け取ることができます．
Fortranでリストを実装する方法は幾通りもありえますが，ここでは要素を文字型配列として持つ方法を採用します．

まず，リストの要素を```list_content_```型の派生型として定義します．


```Fortran
type :: List_content_
    character(:),allocatable :: char
end type
```

次に，リスト要素の配列をメンバーにもつリスト```List_```型配列を作成します．
併せて，初期化用サブルーチンとゲッターを定義しておきます．

```Fortran
    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type

```

また，コマンドライン引数取得用の関数も定義しておきます．

```Fortran
    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface
```

以下が完全な実装です．

```Fortran
module ListClass
    use iso_fortran_env
    implicit none

    type :: List_content_
        character(:),allocatable :: char
    end type

    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type


    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface
contains

! #####################################################
pure subroutine new_list_listclass(this, length)
class(List_), intent(inout) :: this
integer(int32), intent(in) :: length
integer(int32) :: i

if (allocated(this%content)) then
   deallocate (this%content)
end if

allocate (this%content(length))
do i = 1, length
   this%content(i)%char = ""
end do

end subroutine
! #####################################################

! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
    class(List_), intent(in) :: this
    integeR(int32), intent(in) :: idx
    character(:), allocatable :: ret

    if (allocated(this%content))then
       ret = this%content(idx)%char
    elseif (.not. allocated(this%content)) then
       ret = ""
       return
    end if

end function
! #####################################################


! #####################################################
function argv_get_cmd_args_as_list() result(ret)
    type(List_) :: ret
    integer(int32) ::i, n, length, status
    character(:),allocatable :: this_arg
    !character(:),allocatable ::line

    n = command_argument_count()
    call ret%new(n) 
    
    do i = 1,n
       call  get_command_argument(i,length=length,status=status)
       if(allocated(this_arg))then
          deallocate(this_arg)
       endif      
       allocate(character(length)::this_arg)
       
       call  get_command_argument(i,this_arg,status=status)
       
       ret%content(i)%char = this_arg
    enddo
    

 end function
! #####################################################

end module ListClass
```


# 用例
第一引数，第二引数を取得してみます．

```Fortran
program main
    use ListClass
    implicit none
    
    type(List_) :: args

    args = argv()
    print *, args%get(1)
    print *, args%get(2)
    
end program main
```

```./a.out hello world```
と実行すると，無事，以下の出力を得ます．

```shell
$ ./a.out hello world
 hello
 world
```

また，コマンドライン引数を単発で取得する場合にも，ゲッターをユーザー定義演算子化すると，やや明快になります．

```Fortran
    interface operator(.get.)
        module procedure get_element_of_listclass
    end interface
```

```Fortran
 ! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
    character(:),allocatable :: ret
    type(List_),intent(in) :: this_list
    integer(int32),intent(in) :: idx
 
    ret = ""
    if(allocated(this_list%content) )then
       if (size(this_list%content) >= idx )then
          ret = this_list%content(idx)%char
       endif
    endif
 
 end function
 ! #####################################################
```

以下のように実行できます．

```Fortran
program main
    use ListClass
    implicit none
    
    print *, argv() .get. 1
    print *, argv() .get. 2
    
end program main
```
出力は同じく以下の通りとなります．


```shell
$ ./a.out hello world
 hello
 world
```

# まとめと拡張
簡単な派生型を作ることで，コマンドライン引数のリストのようなものを得られるようにしました．
ここで，

```Fortran
   public :: assignment(=)
   interface assignment(=)
      module procedure assign_real64, assign_int32, ...
   end interface
```
のようにして，文字型配列を浮動小数点数型，整数型等に変換できるようにしておくと，よりリストに近いものになります．
また，簡単な拡張で```split```等，文字列とリストを相互に変換することもでき，pythonライクな使用感が得られます．



# Appendix
1ファイルにまとめたコードはこちらになります．

```Fortran
module ListClass
    use iso_fortran_env
    implicit none
    
    !type :: List_content_
    !    character(:),allocatable :: char
    !end type

    type :: List_content_
        character(:),allocatable :: char
    end type

    type :: List_
        type(List_content_), allocatable :: content(:)
    contains
        procedure, public :: new => new_list_listclass
        procedure, public :: get => get_list_content_listclass
    end type


    interface argv
        module procedure argv_get_cmd_args_as_list
    end interface


    interface operator(.get.)
        module procedure get_element_of_listclass
    end interface
contains

! #####################################################
pure subroutine new_list_listclass(this, length)
    class(List_), intent(inout) :: this
    integer(int32), intent(in) :: length
    integer(int32) :: i

    if (allocated(this%content)) then
       deallocate (this%content)
    end if
    
    allocate (this%content(length))
    do i = 1, length
       this%content(i)%char = ""
    end do

end subroutine
! #####################################################

! #####################################################
pure function get_list_content_listclass(this, idx) result(ret)
    class(List_), intent(in) :: this
    integeR(int32), intent(in) :: idx
    character(:), allocatable :: ret

    if (allocated(this%content))then
       ret = this%content(idx)%char
    elseif (.not. allocated(this%content)) then
       ret = ""
       return
    end if

end function
! #####################################################


! #####################################################
function argv_get_cmd_args_as_list() result(ret)
    type(List_) :: ret
    integer(int32) ::i, n, length, status
    character(:),allocatable :: this_arg
    !character(:),allocatable ::line

    n = command_argument_count()
    call ret%new(n) 
    
    do i = 1,n
       call  get_command_argument(i,length=length,status=status)
       if(allocated(this_arg))then
          deallocate(this_arg)
       endif      
       allocate(character(length)::this_arg)
       
       call  get_command_argument(i,this_arg,status=status)
       
       ret%content(i)%char = this_arg
    enddo
    

 end function
! #####################################################

 ! #####################################################
function get_element_of_listclass(this_list,idx) result(ret)
    character(:),allocatable :: ret
    type(List_),intent(in) :: this_list
    integer(int32),intent(in) :: idx
 
    ret = ""
    if(allocated(this_list%content) )then
       if (size(this_list%content) >= idx )then
          ret = this_list%content(idx)%char
       endif
    endif
 
 end function
 ! #####################################################
 

end module ListClass

program main
    use ListClass
    implicit none
    
    type(List_) :: args


    args = argv()
    print *, args%get(1)
    print *, args%get(2)

    print *, argv() .get. 1
    print *, argv() .get. 2
    
end program main
```