제 4 부 — 프로시저와 모듈



10장 · 함수와 서브루틴


Naming the Work



[!attention] 이 장의 목표


지금까지 배운모든 코드는 하나의 program 으로 작성하였다. 프로그램이 짧을 때는 상관없지만, 같은 계산을 여러 번 반복 때나, 하나의 식을 여러 입력값에 대해 평가해야 할 때는 문제가 복잡해 진다. 이 경우, 계산식을 별도의 코드에 정의해 두고 주 프로그램(host)에서 불러내어(call) 사용할 수 있다면, 코드 길이를 줄여 가독성 높게 만들 수 있다.

프로시저(procedure) 가 이 문제를 해결할 수 있다. 프로시저는 이름이 붙은 코드 묶음으로, 함수(function)서브루틴(subroutine) 두 종류가 있다. 한 번만 프로시저로 정의해 두면, 해당 프로시저의 이름만 호출하여 같은 논리 구조를 몇 번이고 반복 사용할 수 있다. 프로시저는 되부름(recursion) 함수를 사용하여 자기 자신을 호출할 수도 있다.


[!important] 10.1 함수와 서브루틴의 정의·호출

프로시저는 "입력을 받아 일을 하고 결과를 돌려주는 부품으로 정의할 수 있다. 함수와 서브루틴은 프로그램의 기능을 독립적인 단위로 분할하고 재사용성을 높이는 코드 모듈화의 핵심 도구이다.

함수와 서브루틴은 결과를 돌려주는 방식에서 다음 명확한 차이가 있다.

프로시저의 인수 전달 메커니즘을 이해하기 위해 아래 주요 용어들을 익혀두자.

1. 함수(Function)

함수는 입력값을 받아 계산한 후, 단 하나의 결과값을 반환할 때 사용한다. 아래와 같이 변수 = 함수_이름([실인수_목록])과 같이 식의 일부나 대입문 우변에서 호출한다.

[형] function 이름 ( [가인수목록] ) [result(결과변수)]
   implicit none
   선언부
   실행부 — 결과변수(또는 이름)에 값을 대입
end function [이름]

[예제] 섭씨를 화씨로 변환 - 함수 사용

1장에서 구현했던 온도 변화 예제를 함수를 써서 구현해 보자. 함수는 주 프로그램 하단에 contains 키워드를 쓰고 그 아래에 정의한다.

%%writefile temperature.f90
program temperature
   implicit none
   integer :: i
   real :: celsius, fahr

   print '(a)', "  celsius   fahrenheit"
   do i = 0, 100, 20
      celsius = real(i)
      fahr = to_fahrenheit(celsius)
      print '(f9.1, f12.1)', celsius, fahr
   end do

contains

   function to_fahrenheit(c) result(f)
      implicit none
      real :: c
      real :: f
      f = c * 9.0 / 5.0 + 32.0
   end function to_fahrenheit

end program temperature

컴파일·실행:

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

실행 결과:

  celsius   fahrenheit
      0.0        32.0
     20.0        68.0
     40.0       104.0
     60.0       140.0
     80.0       176.0
    100.0       212.0

Fortran에서 contains 아래에 정의된 내부 함수는 바깥쪽 주 프로그램의 자원을 공유할 수 있는 권한을 가진다. 이를 호스트 연관(host association) 이라고 한다.

위 예제에서는 함수가 주 프로그램의 변수를 사용하지 않고 함수 내에서 독립적으로 cf를 선언해서 사용했다. 이는 예기치 않은 부작용을 막아주는 매우 안전하고 권장되는 코딩 방식이다.

[!warning] [흔한 실수] 만약 주 프로그램 내 변수명과 함수 내 새로 선언한 변수명 같다면?

주 프로그램과 함수 내부에서 같은 이름의 변수를 선언하면, 함수 내부에서 선언된 변수가 지역 변수로서 우선 적용되어 바깥의 변수를 가려버린다(information hiding). 따라서 함수 안에서 그 이름을 사용하거나 값을 변경하더라도, 이는 함수 내부의 지역 변수에만 영향을 미치며 주 프로그램의 변수에는 아무런 변화가 없다.

예를 들어, 주 프로그램에 real :: c가 있고, 내부 함수 안에도 똑같이 real :: c를 선언한다면, 함수 안에서 값을 바꾸면 함수 내부의 변수만 바뀔 뿐, 주 프로그램의 변수에는 아무런 영향을 주지 않는다.

2. 서브루틴(Subroutine)

서브루틴은 값을 반환하지 않거나, 여러 개의 인수를 통해 다수의 결과값을 변경 하거나 전달할 때 사용한다. 아래와 같이 독립된 문장으로 call 서브루틴_이름([실인수_목록]) 형식을 사용하여 호출한다.

subroutine 이름 ( [가인수목록] )
   implicit none
   선언부
   실행부
end subroutine [이름]

[예제] 원의 넓이·둘레 동시에 구하기 - 서브루틴 사용

원의 넓이와 둘레를 한 번에 구하고 싶다고 하자. 함수는 단 하나의 값만 반환할 수 있으므로 원의 넓이와 둘레를 각각 구하려면 두 번의 함수 호출이 필요하다. 반면 서브루틴은 여러 개의 인수를 활용하여 두 가지 이상의 계산 결과를 한 번에 돌려줄 수 있어 효율적이다.

%%writefile circle_demo.f90
program circle_demo
   implicit none
   real :: r, area, circumference

   r = 2.5
   call circle(r, area, circumference)
   print '(a, f0.4)', "radius        = ", r
   print '(a, f0.4)', "area          = ", area
   print '(a, f0.4)', "circumference = ", circumference

contains

   subroutine circle(radius, a, c)
      implicit none
      real :: radius, a, c
      real, parameter :: pi = 3.14159265
      a = pi * radius**2
      c = 2.0 * pi * radius
   end subroutine circle

end program circle_demo

컴파일·실행:

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

실행 결과:

radius        = 2.5000
area          = 19.6350
circumference = 15.7080

서브루틴 circle은 하나의 입력값(radius)을 받아 연산을 수행한 후, 그 결과를 ac에 각각 저장한다. 이때, 호출하는 주 프로그램의 실인수인 areacircumference가 서브루틴 내부의 가인수인 a, c와 순서대로 일대일 매핑된다. 따라서, 서브루틴 내부에서 ac에 대입한 연산 결과가 호출한 바깥쪽 프로그램의 변수들에 그대로 반영된다.

이 예제는 연산의 오버헤드를 줄이고 관련성 높은 데이터를 한 번에 처리하는 서브루틴의 다중 결과 반환 메커니즘을 잘 보여준다.

[!warning] [흔한 실수] 함수와 서브루틴을 혼동 함수는 식 안에서 y = f(x)로 쓰고, 서브루틴은 call s(...)로 부른다. 함수를 call로 부르거나, 서브루틴을 식 한가운데(y = s(x) + 1)에서 쓰면 컴파일 오류가 난다. "값을 돌려받아 식에 쓰는가(함수)", "호출해서 일을 시키는가(서브루틴)"로 구분하자.


[!important] 10.2 내부·외부·모듈 프로시저

프로시저에는 다음과 같이 세 가지 종류가 있다.

이 프로시저들은 주 프로그램 등에서 호출하여 사용하는데, 호출에 필요한 형식적 규약이나 구조(인수 개수, 타입, 반환값 등)를 인터페이스(interface)라고 한다. 즉, 프로시저는 실제 동작을 수행하는 코드 단위(함수, 서브루틴) 실체이고 인터페이스는 그 실체를 어떻게 불러 쓰는지에 대한 약속이다.

이 절에서는 내부와 외부 프로시저를 설명하고, 모듈 프로시저는 12장에서 다룬다.

1. 내부 프로시저

내부 프로시저는 프로그램 본체 뒤에 contains를 두고 그 아래에 정의한다. 내부 프로시저의 장점은 바깥 프로그램(호스트)에 선언된 변수를 인수로 넘기지 않아도 그대로 읽고 쓸 수 있다는 호스트 결합(host association) 이다.

[예제] 내부 프로시저 - 홑결합 구조

%%writefile host_assoc.f90
program host_assoc
   implicit none
   integer :: counter

   counter = 0
   call bump()
   call bump()
   call bump()
   print '(a, i0)', "counter = ", counter

contains

   subroutine bump()
      implicit none
      counter = counter + 1     ! counter comes from the host
   end subroutine bump

end program host_assoc

컴파일·실행:

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

실행 결과:

counter = 3

2. 외부 프로시저

외부 프로시저는 주 프로그램이나 모듈 등 그 어떤 구조적 단위에도 포함되지 않고 물리적으로 완전히 독립되어 존재하는 프로시저이다. 구조 자체는 직관적이고 단순하지만, 외부 함수를 호출하여 사용할 때는 호출하는 주 프로그램 등에서 해당 함수가 반환하는 데이터 타입(형)을 반드시 명시적으로 선언해 주어야 한다.

[예제] 외부 프로시저 — 인터페이스에 주의

%%writefile external_demo.f90
program external_demo
   implicit none
   real :: triple        ! must declare the external function's type
   print '(f0.2)', triple(4.0)
end program external_demo

function triple(x)
   implicit none
   real :: x
   real :: triple
   triple = 3.0 * x
end function triple

컴파일·실행:

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

실행 결과:

12.00

만약 주 프로그램 내부에서 real :: triple이라는 형식 선언을 누락하면 컴파일러는 외부 함수가 어떤 형태의 데이터를 돌려주는지 전혀 알 수 없다. 이 경우 컴파일러는 반환 타입이 일치하지 않는다는 의미를 담은 다음 오류 메시지를 발생시키며 컴파일을 중단한다.

Error: Return type mismatch of function 'triple' ... (UNKNOWN/REAL(4))

이런 현상이 발생하는 이유는 외부 프로시저가 호출하는 주 프로그램과 분리되어 있어 컴파일러가 그 내부 구조를 자동으로 인지하지 못하는 묵시적 인터페이스(implicit interface) 상태이기 때문이다. 이와 같은 선언 누락의 위험성을 방지하고 프로시저를 안전하게 관리하기 위해서는 명시적 인터페이스(explicit interface)가 확보되어야 한다.

따라서 특별한 이유가 없다면 컴파일러가 함수의 구조와 타입을 자동으로 검증할 수 있는 내부 프로시저나 모듈 프로시저를 기본적으로 사용하는 것이 안전하다.

[!warning] [흔한 실수] 프로시저에도 implicit none을 빠뜨리지 말 것. implicit none는 프로시저마다 각각 따로 써야 한다. 아래 함수는 a에 넣으려던 값을 오타 ar에 넣었는데, implicit none이 없어 ar이 새 변수로 묵인된다. 결과 a는 값이 정해지지 않은 채 쓰레기값(garbage value)을 돌려준다.

fortran function area(r) result(a) real :: r, a ar = 3.14159 * r * r ! typo: ar instead of a a = a ! a is never assigned end function area

implicit none을 넣으면 컴파일러가 오류를 찾아내고, 아래와 같은 메세지를 출력한다. Error: Symbol 'ar' ... has no IMPLICIT type; did you mean 'a'?


[!important] 10.3 반환값과 result

Fortran에서 함수가 연산 결과를 호출하는 프로그램(예: 주 프로그램)으로 돌려주는 방법에는 두 가지 표기 방식이 있다.

[예제] 두 가지 호출 방식의 비교

%%writefile two_styles.f90
program two_styles
   implicit none
   real :: x

   x = 3.0
   print '(a, f6.2)', "square(x) = ", square(x)
   print '(a, f6.2)', "cube(x)   = ", cube(x)

contains

   ! style A: return value through the function name
   real function square(a)
      implicit none
      real :: a
      square = a * a
   end function square

   ! style B: return value through a result variable
   function cube(a) result(y)
      implicit none
      real :: a
      real :: y
      y = a * a * a
   end function cube

end program two_styles

컴파일·실행:

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

실행 결과:

square(x) =   9.00
cube(x)   =  27.00

방식 A는 real function square(a)와 같이 함수 헤더에 반환할 데이터 타입을 직접 명시한다. 반면 방식 B는 함수의 헤더에 타입을 적지 않고, result(y) 구문으로 지정한 결과 변수 y의 타입을 함수 내부의 선언부(real :: y)에서 정의한다.

방식 B를 사용하여 결과 변수에 함수 이름과 전혀 다른 직관적인 이름을 부여하면, 복잡한 함수 본문의 코드를 훨씬 읽기 쉽도록 작성할 수 있다.

[!warning] [흔한 실수] 타입 중복 선언 금지 함수 이름과 result 로 지정된 결과 변수는 컴퓨터 내부에서 완전히 동일한 데이터 타입과 종류 속성을 공유한다. 따라서 두 곳 중 단 한 곳에만 데이터 타입을 선언해야 하며, 만약 함수 헤더와 내부 선언부 양쪽에 모두 타입을 선언하면 컴파일러는 중복 선언 오류를 낸다.


[!important] 10.4 재귀(recursive) 함수와 result

재귀(recursion, 되부름)은 프로시저가 직접 또는 간접적으로 자기 자신을 호출하는 것이다. 이는 큰 문제를 동일한 형태의 더 작은 문제로 쪼개어 해결하는 방식으로, 코드가 간결하고 논리 구조가 깔끔해 진다. 다만 호출 횟수가 많아지면 메모리 사용과 실행 시간이 추가로 소모될 수도 있다.

재귀 함수를 구현할 때는 다음 문법적 규칙을 철저히 따라야 한다.

재귀 함수 선언에서, 다음 세 가지 표기 방식은 모두 문법적으로 동일하다.

표기 방식 문법 예시
방식 1 integer recursive function 이름 (가인수) result(결과변수)
방식 2 recursive integer function 이름 (가인수) result(결과변수)
방식 3 recursive function 이름 (가인수) result(결과변수)

이 책에서는 구조적 명료성을 위해 아래와 같이 방식 3을 사용한다.

recursive function factorial(n) result(res)
    integer, intent(in) :: n
    integer :: res  ! 결과 변수의 형을 선언부에 명시

    if (n <= 1) then
        res = 1
    else
        res = n * factorial(n - 1)
    end if
end function factorial

재귀 함수의 특징과 사용 방법은 다음과 같다.

[예제] 계승(factorial) 계산

재귀함수의 가장 전형적인 예제인 계승을 계산하는 예제 코드를 구현해 보자.

정수 n의 계승 n! = n × (n-1) × … × 1을 되부름으로 계산한다. n이 1 이하이면 결과는 1이고(종료 조건), 그렇지 않으면 n × (n-1)!로 더 작은 문제에 맡긴다.

%%writefile factorial_demo.f90
program factorial_demo
   implicit none
   integer :: n

   print '(a, i0)', "fact(5) = ", fact(5)
   print '(a)', "----------------"
   do n = 0, 8
      print '(i2, a, i7)', n, "! = ", fact(n)
   end do

contains

   recursive function fact(n) result(n_fact)
      implicit none
      integer :: n
      integer :: n_fact
      if (n <= 1) then
         n_fact = 1
      else
         n_fact = n * fact(n - 1)
      end if
   end function fact

end program factorial_demo

컴파일·실행:

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

실행 결과:

fact(5) = 120
----------------
 0! =       1
 1! =       1
 2! =       2
 3! =       6
 4! =      24
 5! =     120
 6! =     720
 7! =    5040
 8! =   40320

프로그램 내부에서 fact(5)가 호출되면 컴퓨터는 즉시 결과를 내지 못하고 $5 \times \text{fact}(4)$라는 연산식을 메모리에 쌓아둔다. 이어서 fact(4)는 $4 \times \text{fact}(3)$으로, fact(3)은 $3 \times \text{fact}(2)$로, fact(2)는 $2 \times \text{fact}(1)$로 문제를 점진적으로 축소하며 자기 자신을 연속적으로 호출한다.

[!warning] [흔한 실수] 되부름 함수·서브루틴에는 recursive를 붙일 것. 자기 자신을 호출하는 프로시저는 함수 머리에 recursive를 붙여야 한다. recursive 키워드를 누락하거나 반환을 위한 result 을 정상적으로 선언하지 않으면, cannot be called recursively, as it is not RECURSIVE 오류가 난다. 참고로, Fortran 2018부터는 프로시저가 기본적으로 되부름 가능해져 recursive 키워드가 필수는 아니다.

[예제] 감쇠 진동 곡선 그래프

사용자가 정의한 함수를 활용하여 물리적인 감쇠 진동 곡선의 데이터를 생성하고, 이를 파일로 저장하여 시각화하는 전체적인 과정을 보여주는 예제이다.

%%writefile plot_function.f90
program plot_function
   implicit none

   call sample_to_csv(400, "curve.csv")
   print '(a)', "curve.csv written"

contains

   function damped(x) result(y)
      implicit none
      real :: x
      real :: y
      y = exp(-0.1 * x) * sin(x)
   end function damped

   subroutine sample_to_csv(npoints, filename)
      implicit none
      integer :: npoints
      character(len=*) :: filename
      integer :: i, u
      real :: x
      real, parameter :: xmax = 20.0
      open(newunit=u, file=filename, status="replace", action="write")
      do i = 0, npoints
         x = real(i) / real(npoints) * xmax
         write(u, '(f10.5, ",", f12.6)') x, damped(x)
      end do
      close(u)
   end subroutine sample_to_csv

end program plot_function

감쇠 진동 f(x) = e^(-0.1x)·sin(x)를 함수로 정의하고, 서브루틴이 그 함수를 여러 지점에서 평가해 curve.csv에 저장한다. 내부 프로시저끼리 호출하는 방법에 주목하자. 서브루틴 sample_to_csv가 같은 호스트의 함수 damped를 호출한다.

contains 문 아래에 함수 damped와 서브루틴 sample_to_csv라는 두 개의 내부 프로시저를 동시에 정의하고 있다. 주목할 점은, 서브루틴 sample_to_csv 내부의 반복문 안에서 damped(x) 함수를 직접 호출하여 사용한다는 것이다. 동일한 주 프로그램 공간에 속한 내부 프로시저들은 호스트를 공유하므로, 별도의 복잡한 연결 과정 없이도 이처럼 서로를 자유롭게 호출하여 협력 연산을 수행할 수 있다.

컴파일·실행:

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

실행 결과 :

curve.csv written

!head curve.csv 명령으로 curve.csv의 앞부분 출력:

0.00000, 0.000000 
0.05000, 0.049730 
0.10000, 0.098840 
0.15000, 0.147213 
0.20000, 0.194735 
0.25000, 0.241296 
0.30000, 0.286786 
0.35000, 0.331104 
0.40000, 0.374149 
0.45000, 0.415826

이와 같은 방식으로 총 400개의 지점에서 $x$값을 0부터 20.0까지 일정하게 변화시키며 계산된 수치 쌍이 데이터 파일curve.csv에 순차적으로 기록된다.

이제 파이썬 코드로 시각화해 보자.

import csv
import matplotlib.pyplot as plt

xs, ys = [], []
with open("curve.csv") as fp:
    for row in csv.reader(fp):
        xs.append(float(row[0]))
        ys.append(float(row[1]))

plt.figure(figsize=(6, 4.5))
plt.plot(xs, ys)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.title("Fortran function, Python plot")
plt.grid(True)
plt.savefig("curve.png", dpi=120)
plt.show()

수학적으로 $f(x) = e^{-0.1x} \sin(x)$ 식은 시간이 흐름에 따라 진폭이 점차 줄어드는 감쇠 진동(damped oscillation)을 나타낸다.

사전에 정의된 damped 함수 내부의 계산 식 한 줄만 원하는 수식으로 변경하면, 데이터 생성 서브루틴이나 시각화 코드를 전혀 수정하지 않고도 다른 함수의 그래프를 즉시 얻을 수 있다. 이것이 바로 계산 프로그램을 프로시저로 분리하여 모듈화했을 때 얻을 수 있는 가장 강력한 구조적 이점이다.

![[Pasted image 20260712153058.png]]


[!important] 오류 학습

프로시저를 처음 배울 때 가장 자주 겪는 실수는 인수를 잘못 넘기는 것이다. 인수의 개수를 빠뜨리거나, 데이터 타입이 다른 값을 넘기는 경우가 대표적이다. 다행히 내부 프로시저는 컴파일러가 그 구조(인터페이스)를 미리 알고 있으므로, 이런 실수를 컴파일 단계에서 정확히 잡아낸다. 두 가지 전형적인 오류 사례를 직접 확인해 보자.

[예제] 인수 개수 부족 오류

서브루틴 circle은 세 개의 인수(radius, a, c)를 필요로 한다. 만약 둘레 계산 결과가 필요 없다는 이유로 인수를 두 개만 넘겨 호출하면 오류가 발생한다.

%%writefile eld_arg_count.f90
program edl_arg_count
   implicit none
   real :: r, area, circ
   r = 2.0
   call circle(r, area)        ! circle needs three arguments
   print *, area
contains
   subroutine circle(radius, a, c)
      implicit none
      real :: radius, a, c
      real, parameter :: pi = 3.14159265
      a = pi * radius**2
      c = 2.0 * pi * radius
   end subroutine circle
end program edl_arg_count

위 코드를 아래와 같이 컴파일하면 에러 메시지가 출력된다.

!gfortran -O2 -std=f2018 -Wall edl_arg_count.f90 -o edl_arg_count
!./edl_arg_count
eld_arg_count.f90:5:61:
    5 | call circle(r, area) ! circle needs three arguments
      |                                                   1 
Error: Missing actual argument for argument ‘c’ at (1)
/bin/bash: line 1: ./eld_arg_count: No such file or directory

이는 세 번째 가인수인 c에 대응하는 실인수가 전달되지 않았다는 의미이다. 설령 둘레 값이 필요하지 않더라도, 결과 값을 받아줄 변수를 반드시 선언하여 세 개의 인수를 모두 넘겨야 한다. 호출부를 call circle(r, area, circ)로 수정하면 정상적으로 컴파일된다.

[예제] 인수 데이터 타입 불일치 오류

함수 to_fahrenheit는 실수형 인수를 받도록 정의되어 있다. 만약 섭씨 100도를 계산하기 위해 정수형 상수인 100을 인수로 넘기면 오류가 발생한다.

%%writefile edl_arg_type.f90
program edl_arg_type
   implicit none
   print '(f0.2)', to_fahrenheit(100)    ! passing integer, dummy is real
contains
   function to_fahrenheit(c) result(f)
      implicit none
      real :: c
      real :: f
      f = c * 9.0 / 5.0 + 32.0
   end function to_fahrenheit
end program edl_arg_type

위 코드를 아래와 같이 컴파일하면 에러 메시지가 출력된다.

!gfortran -O2 -std=f2018 -Wall edl_arg_type.f90 -o edl_arg_type
!./edl_arg_type
edl_arg_type.f90:3:18: 
   3 | print '(f0.2)', to_fahrenheit(100) ! passing integer, dummy is real 
     |               1 
Error: Type mismatch in argument ‘c’ at (1); passed INTEGER(4) to REAL(4) /bin/bash: line 1: ./edl_arg_type: No such file or directory

이는 가인수 c는 실수형 인터페이스를 가지는데, 실인수로 정수형 변수가 전달되었다는 의미이다. 따라서 이 문제를 해결하려면 실인수를 100.0으로 명시하거나, real(100)과 같이 실수형으로 명시적 변환을 거쳐 전달해야 한다.

위 두 예제에서, 인수의 개수를 빠뜨리거나, 데이터 타입이 다른 값을 넘기는 두 가지 오류 사례 모두 실행 단계가 아닌 컴파일 단계에서 확실하게 걸러졌다는 점이 중요하다. circleto_fahrenheit가 모두 주 프로그램 내부에 포함된 내부 프로시저이기 때문에 컴파일러가 인터페이스의 구조를 정확히 파악할 수 있었다.

만약 동일한 프로시저를 명시적 인터페이스 없이 외부 프로시저로 작성했다면, 데이터 타입이 어긋난 호출 문장이 오류 없이 컴파일을 통과하게 된다. 그 결과 프로그램 실행 중에 메모리 해석 오류로 인해 전혀 엉뚱한 값을 출력하는 심각한 논리적 오류를 유발한다. 내부 프로시저나 모듈 프로시저를 기본적으로 사용하도록 권장하는 이유가 바로 여기에 있다. 외부 프로시저를 안전하게 사용하도록 지원하는 명시적 인터페이스의 개념과 구현 방법은 11장에서 다룬다.


[!important] 요약

항목 표기
함수 정의 function name(args) result(r)end function
함수 정의(이름 반환) real function name(args)end function
서브루틴 정의 subroutine name(args)end subroutine
함수 호출 y = name(args)
서브루틴 호출 call name(args)
되부름 함수 recursive function name(args) result(r)
내부 프로시저 호스트의 contains 아래에 정의

[!important] 연습 문제

  1. 화씨를 섭씨로 바꾸는 함수 to_celsius(f)를 작성하고, 32°F부터 212°F까지 20도 간격으로 표를 출력하라.
  2. result 절을 사용해 실수 x의 세제곱을 돌려주는 함수 cube(x)를 작성하라.
  3. 정수 n이 짝수면 .true., 홀수면 .false.를 돌려주는 논리형 함수 is_even(n)을 작성하라.
  4. 두 정수 인수의 값을 서로 맞바꾸는 서브루틴 swap(a, b)를 작성하고, 호출 전후의 값을 출력해 확인하라.
  5. 1부터 n까지의 합을 되부름으로 구하는 함수 sum_to(n)을 작성하라. recursiveresult를 모두 쓰고, n = 10에 대해 결과가 55인지 확인하라.
  6. 반지름 r을 받아 원의 넓이만 돌려주는 함수 circle_area(r)를 작성하고, r = 1.0, 2.0, 3.0에 대해 출력하라.
  7. 반복문으로 계산하는 피보나치 함수와 되부름으로 계산하는 피보나치 함수를 각각 작성하라. n = 0부터 15까지 두 결과가 일치하는지 표로 비교하고, 되부름판이 왜 비효율적인지 설명하라.
  8. 각도(degree)를 라디안으로 바꾸는 함수 deg2rad(deg)를 작성하라. 이를 이용해 0°부터 360°까지 5° 간격의 사인값을 sine.csv로 출력하고, Python으로 곡선을 그려라.
  9. 이차방정식 a·x² + b·x + c = 0의 두 실근을 구하는 서브루틴 quadratic_roots(a, b, c, x1, x2, ok)를 작성하라. 판별식이 음수면 논리값 ok.false.로 돌려주어 실근이 없음을 알리도록 하라.
  10. base와 음이 아닌 정수 지수 n을 받아 거듭제곱을 반복 곱으로 계산하는 함수 power(base, n)을 작성하라(연산자 **를 쓰지 말 것). 같은 값을 base ** n과 비교하라.