제 4 부 — 프로시저와 모듈



12장 · 모듈과 프로그램 구성


Organizing the Whole



[!attention] 이 장의 목표


지금까지는 하나의 프로그램 파일 안에 모든 구성 요소를 전부 담아 작성했다. 변수, 상수, 함수, 서브루틴이 한곳에 모여 있더라도 프로그램의 규모가 작을 때는 큰 문제가 발생하지 않는다. 그러나 코드의 크기가 커지고 복잡해지면 크게 두 가지 구조적인 문제에 직면하게 된다.

첫째, 동일한 코드를 여러 프로그램이 공유하여 재사용하고자 할 때 한계가 발생한다. 원주율 상수인 pi 값이나 사다리꼴 적분 루틴을 세 개의 서로 다른 프로그램에서 똑같이 사용하고 있다고 가정해 보자. 이를 매번 복사하고 붙여넣는 방식을 사용하면, 나중에 수식을 수정하거나 버그를 고칠 때 일부 파일을 빠뜨려 코드 간의 불일치가 생기기 쉽다. 따라서 단 한 번만 정의해 두고 여러 코드에서 안전하게 가져다 쓸 수 있는 표준적인 장치가 필요하다.

둘째, 프로그램 내부의 고유한 상태 정보를 함부로 건드리지 못하도록 안전하게 격리하고 싶을 때가 있다. 예를 들어, 데이터의 통계를 계산하는 함수 내부에 합계 변수를 가지고 있는 경우, 이 내부 변수를 외부에서 임의의 값으로 변경해 버리면 전체 통계 계산값이 달라진다. 따라서 외부에서는 정해진 통로(함수나 서브루틴)를 통해서만 접근하도록 허용하고, 내부 변수는 숨겨서 접근을 차단할 필요가 있다.

이런 두 가지 과제를 해결해 주는 도구가 바로 모듈(module)이다. 모듈은 공통으로 사용되는 상수, 사용자 정의 자료형, 변수, 그리고 다양한 프로시저들을 하나의 독립된 단위로 묶어 둔 코드 패키지이다.


[!important] 12.1 모듈의 구조와 use

모듈은 그 자체로 실행되지 않고, 다른 프로그램이 가져다 쓰는 독립된 부품 역할을 한다. 모듈을 활용하는 본질적인 이유는 코드의 재사용성을 높이고, 데이터를 안전하게 보호하기 위함이다.

모듈은 관련 있는 데이터, 구조체 타입, 프로시저를 하나로 묶어 관리하는 Fortran의 가장 핵심적인 구조이다. 모듈의 기본 골격은 다음과 같이 moduleend module 블록을 사용하여 설계한다.

module module_name
    ! 선언부 및 프로시저 포함
end module [module_name]

다른 프로그램이나 또 다른 모듈에서 해당 모듈을 불러올 때는 use 문을 사용한다. 이때 use 문은 반드시 프로그램이나 모듈 선언부의 맨 위, implicit none보다도 먼저 작성해야 한다.

호출하는 쪽에서 모듈을 불러오는 방식은 다음과 같이 분류할 수 있다.

[!warning] [흔한 실수] use 문은 implicit none 보다 앞에 use 문은 선언부의 맨 위, implicit none 보다 위에 와야 한다. 순서를 바꾸면 Error: USE statement at (1) cannot follow IMPLICIT NONE statement at (2) 로 컴파일이 거부된다.

[예제] 상수 모듈과 선택 가져오기(use ... only)

물리학과 기하학에서 자주 사용하는 상수를 하나의 모듈에 모아 정의하고, 메인 프로그램에서 필요한 상수만 선택적으로 가져와 활용하는 예제이다. 수치 계산의 정밀도가 중요한 상수를 다루므로 iso_fortran_env 모듈의 real64(배정밀도) 타입을 지정하여 선언한다.

%%writefile constants_mod.f90
module constants_mod
   use iso_fortran_env, only: real64
   implicit none
   private

   public :: real64
   public :: pi, two_pi, deg_to_rad

   real(real64), parameter :: pi = 3.141592653589793_real64
   real(real64), parameter :: two_pi = 2.0_real64 * pi
   real(real64), parameter :: deg_to_rad = pi / 180.0_real64
end module constants_mod


program use_constants
   use constants_mod, only: real64, pi, deg_to_rad
   implicit none
   real(real64) :: angle_deg, angle_rad

   angle_deg = 60.0_real64
   angle_rad = angle_deg * deg_to_rad

   print '(a, f12.8)', "pi        = ", pi
   print '(a, f8.2, a, f12.8)', "deg ", angle_deg, " -> rad ", angle_rad
   print '(a, f12.8)', "sin(60deg)= ", sin(angle_rad)
end program use_constants

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall constants_mod.f90 -o use_constants
!./use_constants

실행 결과:

pi        =   3.14159265
deg    60.00 -> rad   1.04719755
sin(60deg)=   0.86602540

하나의 소스코드 파일 안에 moduleprogram을 함께 배치하여 컴파일하면, 컴파일러는 의존성 구조에 따라 module을 먼저 컴파일한 뒤 이를 참조하는 program 영역을 그 다음에 빌드한다.

모듈 내부에서 외부로부터 가져온 real64 타입을 public :: real64 구문을 통해 다시 외부로 공개했다. 이처럼 다른 모듈에서 가져온 식별자 이름을 재공개하면, 이를 호출하는 메인 프로그램 측에서는 iso_fortran_env 모듈을 번거롭게 직접 불러올 필요 없이 constants_mod 하나만 참조하는 것만으로 데이터 정밀도 설정(real64)까지 한 번에 해결할 수 있어 코드가 간결해진다.


[!important] 12.2 식별자 충돌 방지와 이름 변경(rename) 기법

모듈을 사용하여 계산 프로그램을 설계하다 보면, 서로 다른 모듈에 동일한 이름의 상수나 변수(예: pi)가 동시에 선언되어 이름 충돌(name conflict)이 발생할 수 있다.

이를 방지하기 위해, Fortran 은 use 문장 뒤에 화살표 연산자 =>를 써서, 가져올 변수의 이름을 프로그램 단위 내부에서 사용할 새로운 이름으로 변경하여 수령하는 이름 변경(rename) 을 제공한다.

[예제] 이름 충돌 해결과 화살표(=>) 이름 변경 활용

%%writefile rename_demo.f90
module geo_constants
   implicit none
   real, parameter :: pi = 3.14159265
end module geo_constants

module phys_constants
   implicit none
   real, parameter :: pi = 3.1415927
   real, parameter :: c_light = 2.99792458e8
end module phys_constants


program rename_demo
   use geo_constants, only: geo_pi => pi
   use phys_constants, only: phys_pi => pi, c_light
   implicit none

   print '(a, f12.8)', "geo_pi  = ", geo_pi
   print '(a, f12.8)', "phys_pi = ", phys_pi
   print '(a, es12.4)', "c_light = ", c_light
end program rename_demo

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall rename_demo.f90 -o rename_demo
!./rename_demo

실행 결과:

geo_pi  =   3.14159274
phys_pi =   3.14159274
c_light =   2.9979E+08

실무에서 개발자가 배포한 외부 라이브러리나 모듈을 가져와 조합하는 일이 많다. 이때 각기 다른 파일에 선언된 물리 변수들이 우연히 겹쳐 컴파일 단계에서 오류를 내는 현상을 막기 위해, 소스 코드를 직접 수정하여 변수명을 바꾸는 위험을 감수하는 대신 => 연산자를 전면에 배치하는 것이 코드의 안전성 측면에서 정석적인 코딩 전략이다.

[!warning] [흔한 실수] 모듈 간의 순환 참조 오류

모듈을 설계할 때 가장 주의해야 할 구조적 결함은 순환 참조이다. 예를 들어, 모듈 A가 기능을 구현하기 위해 모듈 B를 use로 불러오고, 동시에 모듈 B 역시 모듈 A의 요소를 사용하기 위해 상호 참조하는 형태를 취하면 컴파일러는 다음 에러 메시지가 출력한다.

Cannot open module file 'b_mod.mod' for reading at (1): No such file or directory 컴파일러는 각 모듈을 빌드할 때 참조하는 상대방 모듈의 컴파일 결과물(.mod 파일)이 먼저 만들어져 있어야 할 것을 요구하는데, 순환 참조는 두 모듈이 서로를 기다리는 교착 상태에 빠지게 된다.

이를 해결하려면, 두 모듈이 공통으로 참조하는 상수나 자료형을 분리하여 제3의 독립된 모듈(예: common_types_mod)로 만들어 둔다. 그 후 모듈 A와 모듈 B가 각각 이 제3의 모듈을 use 하도록 한다.


[!important] 12.3 public, private 그리고 캡슐화

모듈이 선언하는 식별자들은 기본적으로 모두 공개(public) 상태이다. 즉, 외부 프로그램에서 use 문으로 불러오면 제약 없이 모두 접근할 수 있다. 그러나 모듈의 규모가 커질수록 외부에 제공할 핵심 기능과 내부 구현을 위해 숨겨야 할 전역 변수 등을 철저히 구분해야 한다. 이런 소프트웨어 설계 기법을 캡슐화(encapsulation) 라고 한다.

모듈을 작성할 때는, 기본 접근성을 private으로 비공개 전환하고, 외부에 노출할 인터페이스만 public으로 선언하여 여는 것을 권장한다. 이 방식을 적용하면 개발자가 새로운 내부 변수나 함수를 추가할 때, 접근성 지정을 실수로 누락하더라도 외부로 불필요한 데이터가 노출되는 것을 방지할 수 있다.

Fortran에서 모듈 내 요소들의 접근성을 지정하는 방법은 크게 '문장 형태'와 '속성 형태' 두 가지가 있다.

1. 문장 형태 지정 (Statement Form)

모듈 선언부 상단의 독립된 명령문으로 접근성을 선언하는 방식이다. private 또는 public 키워드를 단독으로 선언하면 모듈 전체의 기본(default) 접근성이 변경된다.

2. 속성 형태 지정 (Attribute Form)

변수나 상수를 선언하는 문장에 접근성 키워드를 속성으로 직접 결합하는 방식이다. 아래와 같이 자료형 선언문 내에 쉼표로 구분하여 기술한다.

[예제] 캡슐화를 적용한 계수기(counter) 구현

모듈 내부에서 상태 값을 유지하는 전역 변수 count는 철저히 은폐(private)하고, 값의 초기화, 증가, 현재 상태 조회 기능은 검증된 공개(public) 프로시저 인터페이스를 통해서만 제어하는 예제이다.

%%writefile counter_mod.f90
module counter_mod
   implicit none
   private

   integer :: count = 0

   public :: reset_counter, increment, current_count

contains

   subroutine reset_counter()
      count = 0
   end subroutine reset_counter

   subroutine increment(step)
      integer, intent(in), optional :: step
      if (present(step)) then
         count = count + step
      else
         count = count + 1
      end if
   end subroutine increment

   function current_count() result(value)
      integer :: value
      value = count
   end function current_count

end module counter_mod


program demo_counter
   use counter_mod, only: reset_counter, increment, current_count
   implicit none

   call reset_counter()
   call increment()
   call increment()
   call increment(step=5)

   print '(a, i0)', "current count = ", current_count()
end program demo_counter

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall counter_mod.f90 -o demo_counter
!./demo_counter

실행 결과:

current count = 7

이 구조에서 내부 핵심 상태 변수인 countprivate 속성으로 비공개되어 있기 때문에, 메인 프로그램 영역에서 count = 10과 같이 직접 값을 수정하려고 하면 컴파일러가 빌드를 거부한다.

호출 측 문맥에서는 오직 모듈이 공식적으로 제공하는 세 개의 공개 프로시저만을 조합하여 상태를 안전하게 제어한다. 인수를 생략하여 기본값 1씩 두 번 증가시킨 뒤, 키워드 인수를 활용하여 값을 5만큼 추가로 더한 연산 과정($1 + 1 + 5 = 7$)이 캡슐화된 내부에서 정확하게 수행됨을 확인할 수 있다.

[!note] 호스트 결합(Host Association) Fortran에서 상위 구조(호스트)에 선언된 자원을 하위 구조(내부 프로시저)가 그대로 물려받아 사용하는 변수 공유 메커니즘을 뜻한다.

모듈 선언부에 정의한 상수, 변수, 사용자 정의 자료형은 해당 모듈의 contains 구문 아래에 포함된 모든 프로시저(함수 및 서브루틴)에서 별도의 선언 없이 자동으로 인식된다.

1. 호스트 결합 비사용 함수가 일을 할 때마다 외부에서 매번 pi 값을 알려줘야 한다.

Fortran ! pi를 매번 인수로 넘겨주어야 하는 번거로움 function calc_area(r, pi) result(area) real :: r, pi, area area = pi * r**2 end function calc_area

2. 호스트 결합 사용 모듈 내에 상수를 고정해 두면, contains 아래 함수는 인수로 pi를 받지 않아도 원래 함수 안에 있던 것처럼 가져다 쓸 수 있다.

```Fortran module circle_mod real :: pi = 3.141592 ! <--- 공용 참조!

contains

function calc_area(r) result(area) real :: r, area ! pi를 인수로 받지 않았지만, 위에 선언되어 있으므로 그냥 그냥 보인다! area = pi * r**2 end function calc_area

end module circle_mod ```

[예제] 비공개 변수 직접 접근 시 컴파일러 차단 확인

private으로 감춰진 모듈 내부 변수 count에 메인 프로그램이 정해진 인터페이스를 거치지 않고 임의로 값을 직접 대입하려고 시도할 때, 컴파일러가 이를 어떻게 차단하는지 확인해 보자.

%%writefile bad_access.f90
program bad_access
   use counter_mod
   implicit none

   count = 100
   print *, count
end program bad_access

먼저, 이전 예제에서 작성한 demo_counter.f90을 오브젝트 파일로 컴파일한 뒤, 이를 연동하여 메인 프로그램을 컴파일한다.

!gfortran -std=f2018 -c demo_counter.f90
!gfortran -std=f2018 bad_access.f90 demo_counter.o -o bad_access

컴파일러 메시지:

bad_access.f90:5:8:

    5 |    count = 100
      |        1
Error: Symbol 'count' at (1) has no IMPLICIT type

변수 count가 모듈 내부에서 private 속성으로 선언되어 있기 때문에, 메인 프로그램에서 use counter_mod 문장으로 모듈을 불러오더라도 식별자 count는 현재 프로그램 안으로 들어오지 못한다.

결과적으로 메인 프로그램 입장에서는 count라는 변수가 아예 존재하지 않는 상태가 되며, count는 자료형이 지정되지 않은 선언으로 판정되어 컴파일 단계에서 차단된다. 결과적으로, 캡슐화 장벽이 내부 상태 정보를 안전하게 보호하였다.


[!important] 12.4 모듈 변수와 contains

contains 구문은 모듈의 선언부와 실제 연산을 수행하는 프로시저부를 구조적으로 분리하는 역할을 한다. 이 contains 구문에서 선언되는 모든 변수와 상수를 모듈 변수라고 부르며, 다음 두 가지 특성을 지닌다.

모듈 변수와 프로시저를 구조화는 문법 규칙은 다음과 같다.

module ⟨이름⟩
   implicit none
   private
   ⟨모듈 변수 선언⟩       ← contains 위: 모든 프로시저가 공유, 수명 길다
   public :: ...
contains
   ⟨function / subroutine 들⟩   ← 모듈 변수를 인수 없이 직접 사용
end module ⟨이름⟩

[예제] 캡슐화와 모듈 변수를 활용한 누적 통계 모듈

이 예제는 실시간으로 값을 하나씩 전달받을 때마다 데이터의 개수, 평균, 분산을 동적으로 갱신해 주는 모듈이다. 통계 계산을 유지하기 위한 핵심 데이터인 합계(total), 제곱합(total_sq), 그리고 데이터 개수(n)를 모듈 변수로 선언하고 private 속성으로 숨겨 외부의 임의 조작을 원천 차단한다.

%%writefile running_stats_mod.f90
module running_stats_mod
   use iso_fortran_env, only: real64
   implicit none
   private

   integer       :: n = 0
   real(real64)  :: total = 0.0_real64
   real(real64)  :: total_sq = 0.0_real64

   public :: stats_reset, stats_add, stats_count, stats_mean, stats_variance

contains

   subroutine stats_reset()
      n = 0
      total = 0.0_real64
      total_sq = 0.0_real64
   end subroutine stats_reset

   subroutine stats_add(x)
      real(real64), intent(in) :: x
      n = n + 1
      total = total + x
      total_sq = total_sq + x * x
   end subroutine stats_add

   function stats_count() result(c)
      integer :: c
      c = n
   end function stats_count

   function stats_mean() result(m)
      real(real64) :: m
      if (n == 0) then
         m = 0.0_real64
      else
         m = total / real(n, real64)
      end if
   end function stats_mean

   function stats_variance() result(v)
      real(real64) :: v, m
      if (n < 2) then
         v = 0.0_real64
      else
         m = stats_mean()
         v = (total_sq - real(n, real64) * m * m) / real(n - 1, real64)
      end if
   end function stats_variance

end module running_stats_mod


program demo_stats
   use running_stats_mod
   use iso_fortran_env, only: real64
   implicit none
   real(real64) :: samples(5) = [2.0_real64, 4.0_real64, 4.0_real64, &
                                 6.0_real64, 4.0_real64]
   integer :: i

   call stats_reset()
   do i = 1, size(samples)
      call stats_add(samples(i))
   end do

   print '(a, i0)',   "count    = ", stats_count()
   print '(a, f8.4)', "mean     = ", stats_mean()
   print '(a, f8.4)', "variance = ", stats_variance()
end program demo_stats

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall running_stats_mod.f90 -o demo_stats
!./demo_stats

실행 결과:

count    = 5
mean     =   4.0000
variance =   2.0000

입력 데이터 $[2, 4, 4, 6, 4]$의 평균은 4이다. 표본분산은 편차 제곱합을 자유도 $n - 1$로 나눈 수식 $\frac{(2-4)^2 + (4-4)^2 + (4-4)^2 + (6-4)^2 + (4-4)^2}{5-1} = \frac{4+0+0+4+0}{4} = 2$가 되어 연산 결과와 일치한다.

이 프로그램의 핵심은 메인 프로그램(호출자) 측에서 내부의 합계 변수나 제곱합 변수의 존재를 알지 못한 채, 오직 stats_add라는 단 하나의 공개된 통로만을 이용하여 데이터를 안전하게 누적했다는 점이다. 모듈 변수가 각 프로시저 호출 사이에도 메모리 상에 계속 살아남아 지속적으로 누적 연산을 이어가는 Fortran 모듈의 특징을 보여준다.

[예제] 누적 사다리꼴 적분과 시각화

이 예제에서는 수치 적분 루틴을 calculus_mod 모듈로 분리한다. 본문 프로그램은 그 모듈을 가져다 cos(x) 를 누적 적분하고, 결과를 csv 로 저장한다. 정확한 답은 sin(x) 이므로 이 둘을 함께 저장해 비교하고, 마지막에 시각화한다.

%%writefile integrate_demo.f90
module calculus_mod
   use iso_fortran_env, only: real64
   implicit none
   private

   public :: real64
   public :: linspace, cumulative_trapz

contains

   subroutine linspace(a, b, x)
      real(real64), intent(in)  :: a, b
      real(real64), intent(out) :: x(:)
      integer :: i, n
      n = size(x)
      do i = 1, n
         x(i) = a + (b - a) * real(i - 1, real64) / real(n - 1, real64)
      end do
   end subroutine linspace

   subroutine cumulative_trapz(x, y, c)
      real(real64), intent(in)  :: x(:), y(:)
      real(real64), intent(out) :: c(:)
      integer :: i
      c(1) = 0.0_real64
      do i = 2, size(x)
         c(i) = c(i - 1) + 0.5_real64 * (y(i) + y(i - 1)) * (x(i) - x(i - 1))
      end do
   end subroutine cumulative_trapz

end module calculus_mod


program integrate_demo
   use calculus_mod
   implicit none
   integer, parameter :: n = 200
   real(real64) :: x(n), y(n), c(n)
   real(real64) :: two_pi
   integer :: i, u

   two_pi = 8.0_real64 * atan(1.0_real64)

   call linspace(0.0_real64, two_pi, x)
   y = cos(x)
   call cumulative_trapz(x, y, c)

   open(newunit=u, file="integral.csv", status="replace", action="write")
   write(u, '(a)') "x,numerical,exact"
   do i = 1, n
      write(u, '(f12.6, ",", f12.6, ",", f12.6)') x(i), c(i), sin(x(i))
   end do
   close(u)

   print '(a)', "integral.csv written"
   print '(a, es12.4)', "max abs error = ", maxval(abs(c - sin(x)))
end program integrate_demo

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall integrate_demo.f90 -o integrate_demo
!./integrate_demo

실행 결과:

integral.csv written
max abs error =   8.3074E-05

!head ./integral.csv 명령으로 생성 파일을 열어보면 아래와 같다.

x,numerical,exact
    0.000000,    0.000000,    0.000000
    0.031574,    0.031566,    0.031569
    0.063148,    0.063100,    0.063106
    0.094721,    0.094572,    0.094580
    0.126295,    0.125949,    0.125960
    0.157869,    0.157201,    0.157214
    0.189443,    0.188296,    0.188312
    0.221017,    0.219203,    0.219222
    0.252590,    0.249892,    0.249913

이제 파이썬으로 시각화 하자.

import csv
import matplotlib.pyplot as plt

x, num, exact = [], [], []
with open("integral.csv") as f:
    reader = csv.reader(f)
    next(reader)  # skip header
    for row in reader:
        x.append(float(row[0]))
        num.append(float(row[1]))
        exact.append(float(row[2]))

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(x, exact, linewidth=3, alpha=0.4, label="exact: sin(x)")
ax.plot(x, num, linewidth=1.2, label="numerical (trapz)")
ax.set_xlabel("x")
ax.set_ylabel("integral of cos(x)")
ax.set_title("Module routine (Fortran) + Visualization (Python)")
ax.legend()
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig("ch12_integral.png", dpi=120)
plt.show()

아래 그림에서 수치 적분 곡선이 최대 오차는 약 8.3×10⁻⁵ 로 sin(x) 곡선과 거의 일치한다. 적분 루틴은 calculus_mod 안에 있고 주 프로그램은 그것을 가져다 쓰기만 한다.

![[Pasted image 20260714113647.png]]


[!important] 오류 학습

모듈을 처음 쓸 때 거의 모두가 한 번씩 겪는 두 가지 오류가 있다. 하나는 컴파일러가 즉시 찾아 주지만, 더 심각한 오류는 컴파일은 통과해도 계산 결과에 오류가 나는 것이다.

[예제] 이름 충돌 발생과 모듈 간 식별자 모호성 확인

기하학용 상수 모듈과 물리학용 상수 모듈이 각각 정의되어 있고, 두 모듈 모두 pi라는 동일한 이름(식별자)을 외부에 공개(public)하고 있는 상황을 가정해 보자. 두 모듈의 기능이 모두 필요하여 only 없이 전체 가져오기(use) 방식을 사용하면 다음과 같이 이름 충돌 문제가 발생한다.

%%writefile clash.f90
module geo_mod
   implicit none
   real, parameter :: pi = 3.14159265
end module geo_mod

module phys_mod
   implicit none
   real, parameter :: pi = 3.1415927
end module phys_mod

program clash
   use geo_mod
   use phys_mod
   implicit none
   print *, pi
end program clash

위 코드는 특별한 문제가 없어 보이지만, 컴파일 단계에서 다음과 같이 에러가 발생한다.

!gfortran -std=f2018 -Wall clash.f90 -o clash
clash.f90:15:14:

   15 |    print *, pi
      |              1
Error: Name 'pi' at (1) is an ambiguous reference to 'pi' from module 'geo_mod'

두 개 이상의 모듈이 동일한 식별자 이름을 외부로 노출하고 있을 때, 주 프로그램에서 이를 무분별하게 가져오면, 식별자 이름 충돌이 발생한다. 이 상태에서, 컴파일러는 이것이 geo_mod에서 온 상수인지 phys_mod에서 온 상수인지 결정하지 못하여 모호한 참조(ambiguous reference) 오류를 출력한다.

이 문제는 아래와 같이 선택적 가져오기와 => 연산자를 활용하여 프로그램 내부에서 사용할 고유한 지역 이름을 다르게 부여함으로써 해결할 수 있다.

program clash_fixed
   use geo_mod, only: geo_pi => pi
   use phys_mod, only: phys_pi => pi
   implicit none
   print *, geo_pi, phys_pi
end program clash_fixed

이처럼 코드를 수정하면, 모듈에서 가져온 두 상수가 주 프로그램 문맥 내에서 geo_piphys_pi라는 독립된 지역 이름으로 분리되므로, 컴파일러가 대상을 명확하게 식별할 수 있다.

실행 결과:

3.14159274 3.14159274


[!important] 요약


[!important] 연습 문제

  1. 길이·질량 단위 환산 상수(예: 인치→센티미터, 파운드→킬로그램)를 담은 모듈 units_mod 을 만들고, 프로그램에서 use ..., only: 로 한 상수만 가져와 환산값을 출력하라.
  2. 모듈 circle_constsphere_const 가 모두 pi 를 공개한다고 하자. 두 모듈을 모두 가져오되 => 로 이름을 구분해 충돌 없이 출력하는 프로그램을 작성하라.
  3. 예제 12-3 의 카운터에 1 감소 서브루틴 decrement 를 추가하고 공개하라. 증가·감소를 섞어 호출한 뒤 최종값을 출력하라.
  4. 예제 12-3 에서 countpublic 으로 바꾸면 어떤 일이 가능해지는지 설명하고, 그것이 왜 캡슐화 관점에서 바람직하지 않은지 서술하라.
  5. 모듈 변수 call_count 를 둔 모듈을 만들어, 그 모듈의 어떤 프로시저가 호출될 때마다 1 씩 증가시키고 횟수를 조회하는 함수를 공개하라.
  6. 예제 12-1 의 상수 모듈에 euler_e(자연상수 e, real64)를 추가하고, 프로그램에서 exp(1.0_real64) 와 비교 출력하라.
  7. 벡터 연산 모듈 vec_mod 을 만들어 내적을 직접 구현한 함수와 2-노름을 구하는 함수를 공개하라. 길이 4 벡터로 검증하되, 내장 dot_product 와 값이 같은지 확인하라.
  8. 예제 12-4 의 누적 통계 모듈에 최솟값·최댓값을 함께 추적하는 기능을 추가하라(모듈 변수 두 개를 더하고 조회 함수를 공개). 임의의 다섯 값으로 검증하라.
  9. running_stats_mod 을 재사용해, 서로 다른 두 데이터 집합에 대해 각각 평균·분산을 구하는 프로그램을 작성하라. 두 번째 집합을 처리하기 전 반드시 초기화하라.
  10. 그림 예제 1 의 calculus_mod 을 재사용해 1/(1+x²) 을 누적 적분하고 결과를 csv 로 내보내라. Python 으로 그려 0 에서 x 까지의 적분이 arctan(x) 에 가까운지 확인하라.