Fortran은 1954년부터 시작된 매우 깊은 유서를 자랑하는 프로그래밍 언어입니다.
ALGOL, A언어 등과 같은 언어는 사실상 수명을 다한 것에 반해, 포트란은 기본적으로 처리 속도로 인해, 현대에 이르러서도 적지않은 사람들이 사용하고 있습니다.


이러한 사례처럼 현재까지도 다양한 분야와 기기에서 포트란이 활용되고 있습니다.
그러기에, 포트란 또한 현대에 발을 맞춘 표준들이 등장하기 시작하였는데, 그 중에 하나가 OOP입니다.
포트란은 2003년에 Fortran 90 개정을 잇는 Fortran 2003 개정을 발표합니다.
간단하게 발표된 대표적인 개정들은
- OOP 지원
- IEEE 부동소수점 표준 내장 모듈
- 비동기 전송, 스트림 타입 I/O 도입
- 명령 라인 지원
- 프로시저 포인터
- 데이터 조작성 강화
- C언어와 상호환성 BINDING
- 할당 특성 강화
이렇게 있습니다.
대표적으로 눈에 띄이는 것이 OOP입니다.
그럼 과연 포트란에서는 OOP를 어떻게 사용하는지 알아볼까요?
OOP를 사용해보자
사실 OOP는 Fortran 90 개정부터 천천히 시도해보기 시작했습니다.
정확히는 OOP인 척을 하였죠.
대체적으로 Fortran 90/95에서의 OOP는 C와 매우 유사합니다.
바로 구조체를 생성하고 그 구조체에 해당하는 메소드를 정의해줍니다.
예를 들면 이런식으로 말이죠.
module Circle_Class
implicit none
public :: Circle, print_radius
type Circle
Real :: radius
end type Circle
contains
subroutine print_radius(this)
type(Circle) :: this
Write(*,*) "Radius: ", this%radius
end subroutine print_radius
end module Circle_Class
program class_test
use Circle_Class
implicit none
type(Circle) :: c
c = Circle(2)
call print_radius(c)
Write(*,*) "Hello Word!"
end program class_test
대략적인 실행 결과는 다음과 같습니다.

하지만 이렇게 작성하면 발생하는 문제점이 있습니다.
바로 정의하는 메소드를 콜하고자 할 때, 기본적으로 첫번째 매개변수에 자신이 넣고자 하는 타입의 매개변수를 무조건 넣어줘야 합니다.
또한, 모듈 내에서 정의행하기에, 타입과 메소드 전부 public 시켜주어야 합니다. 매우 번거롭기만 하지요.
말이 객체지향 프로그래밍이지, 사실상 객체지향 프로그래밍이라고 부르기 민망하죠.
그렇기에, 제대로 된 객체지향을 위해 새로 개정을 하면서, 2003 개정에서는 이럴 필요는 없으나, 다른 언어와 달리 간단하면서도 어렵습니다.
그럼 한번 알아봅시다.
우선 이를 위해서는 procedure를 어느 정도 이해하셔야 합니다.
procedure는 간단하게 설명드리면, 함수 포인터라고 할 수 있습니다.
예시 소스코드를 보여드리겠습니다.
subroutine Runge_Kutta_Method(f, y0, h, range)
procedure(Real) :: f
Real :: y0, h, x, y, k1, k2, k3, k4
Integer :: range
dx = h
y = y0
x = 0
Open(19, FILE="ode_result.dat", status="NEW")
Do 13, I = 1, (range * INT(1/dx))
k1 = f(x, y)
k2 = f(x + Real(1) / 3 * h, y + Real(1) / 3 * k1 * h)
k3 = f(x + 0.5 * h, y - Real(1) / 3 * k1 * h + k2 * h)
k4 = f(x + h, y + k1 * h - k2 * h + k3 * h)
Write(19, *) x, y
x = x + dx
y = y + h * (k1 + 3 * k2 + 3 * k3 + k4) / 8
13 continue
Close(19)
contains
end subroutine Runge_Kutta_Method
이런식으로 procedure는 함수 포인터로 이용이 가능합니다.
그럼 이를 토대로 다시 한번 작성해볼까요?
module Circle_Class
implicit none
public :: Circle
type Circle
Real :: radius
contains
procedure :: print => print_radius
end type Circle
contains
subroutine print_radius(this)
class(Circle), intent(in) :: this
Write(*,*) "Radius: ", this%radius
end subroutine print_radius
end module Circle_Class
이런식으로 바꾸었습니다.
약간 변경이 되었는데, 천천히 알아볼까요?
우선 type 부분이 바뀌었습니다.
type Circle
Real :: radius
contains
procedure :: print => print_radius
end type Circle
2003 버전 이후 type statement에서도 contains가 사용이 되면서 내부 메소드 정의가 가능해졌습니다.
여기서 주의해야할 것은 contains 부문에서 직접적인 선언은 불가합니다.
이게 무슨 뜻일까요?
말그대로 contains 내부에서 함수에 대한 정의는 불가합니다.
그렇기에, 모듈의 contains 내부에서 함수를 정의하고, 그 정의된 함수를 type 구문의 contains에 함수 포인터 형식(procedure)으로 연결해주어야 합니다.
사실상 말이 메소드 정의이지, 함수 포인터 형태의 변수 하나를 넣는 것입니다.
즉 위의 Circle이라는 타입은 print 메소드가 print_radius라는 함수로 정의하도록 연결해준 것입니다.
달라진 것은 print_radius 부분도 있습니다.
subroutine print_radius(this)
class(Circle), intent(in) :: this
Write(*,*) "Radius: ", this%radius
end subroutine print_radius
여기서는 인자 this의 타입이 type(Circle)에서 class(Circle)로 바뀌었습니다.
이는 Circle을 class로 선언하였으며, in 형식으로 주어, 값은 가져오는게 되나, 대입은 안 되도록 합니다.
그럼 이렇게 되면, 아까처럼 첫번째 인자에 변수를 넣어야 할까요?
이는 예제 소스를 보면
program class_test_2003
use Circle_Class
implicit none
type(Circle) :: c
c = Circle(2)
call c%print
Write(*,*) "Hello Word!"
end program class_test_2003
print 메소드 호출에 첫번째 인자가 필요하지 않다는 것을 알 수 있습니다.
이런 형식으로 포트란에서의 객체지향 프로그래밍을 해보았습니다.
소스코드
깃허브에 올려놓았습니다.
링크