모듈 만들기: 코드 편

모듈 만들기: 코드 편 (Making modules: the code)

모듈이라는 개념은 처음에는 파일 하나로 착각하기 쉬워요. 그런데 Raku에서 .rakumod 파일은 그냥 일반 파일일 뿐이에요. need, use, require를 쓰면 그때 로드되고 컴파일되는 평범한 파일이죠. 이 문서에서 파일과 모듈이 실제로 어떻게 연결되는지 코드를 중심으로 살펴볼게요.

출처: Raku Documentation — Making modules: the code

본문

디스크 위의 모듈 (Modules on disk)

.raku.rakumod 파일을 "모듈"이라고 부르기도 하지만, 사실 이런 파일은 need, use, require를 썼을 때 로드되고 컴파일되는 평범한 파일일 뿐이에요.

.rakumod 파일이 지금까지 우리가 써온 의미의 모듈을 제공하려면, 위에서 설명한 대로 module로 그 모듈을 선언해야 해요. 예를 들어 Foo.rakumod 안에 module M을 넣으면, 우리는 그 모듈을 다음과 같이 로드하고 사용할 수 있어요.

use Foo;                # Foo.rakumod를 찾아서 need 후 import 실행
say M::loud-greeting;   # OUTPUT: «GREETINGS, CAMELIA!␤»
say friendly-greeting;  # OUTPUT: «Greetings, friend!␤»

여기서 파일 이름과 모듈 이름이 분리(depoupling) 되어 있다는 점을 눈여겨보세요. .rakumod 파일 하나가 임의의 식별자를 가진 모듈을 0개 이상 선언할 수 있어요.

파일과 모듈 이름 정하기 (File and module naming)

보통은 .rakumod 파일 하나가 단일 모듈 하나만 제공하길 원해요. 이때 흔한 관례는 파일의 기본 이름(base name)이 모듈 이름과 일치하게 만드는 거예요. 아까의 Foo.rakumod로 돌아가보면, 이 파일은 M이라는 모듈 하나만 제공해요. 그렇다면 MFoo로 바꾸고 싶을 수 있어요. 수정된 파일은 이렇게 읽혀요.

module Foo {
  sub greeting ($name = 'Camelia') { "Greetings, $name!" }
  our sub loud-greeting (--> Str)  { greeting().uc       }
  sub friendly-greeting is export  { greeting('friend')  }
}

이렇게 하면 호출하는 쪽에서 더 일관성 있게 사용할 수 있어요. use FooFoo::의 관계를 눈여겨보세요.

use Foo;
say Foo::loud-greeting;  # OUTPUT: «GREETINGS, CAMELIA!␤»
say friendly-greeting;   # OUTPUT: «Greetings, friend!␤»

만약 Foo.rakumod를 소스 트리 더 깊은 곳, 예를 들어 lib/Utils/Foo.rakumod에 둔다면, 일관성을 유지하기 위해 모듈 이름을 Utils::Foo로 지을 수도 있어요.

unit 키워드

단일 모듈만 제공하는 파일은 unit 키워드로 더 간결하게 작성할 수 있어요. unit module은 남은 컴파일 유닛 전체가 선언된 모듈의 일부임을 지정해요. unit으로 다시 쓴 Foo.rakumod를 볼게요.

unit module Foo;

sub greeting ($name = 'Camelia') { "Greetings, $name!" }
our sub loud-greeting (--> Str)  { greeting().uc       }
sub friendly-greeting is export  { greeting('friend')  }

unit 선언 뒤의 모든 내용이 Foo 모듈 정의의 일부가 돼요.

(unitclass, grammar, role과 함께 쓸 수도 있어요.)

module을 빼먹으면 어떻게 될까요? (What happens if I omit module?)

Foo.rakumod에서 module 선언이 실제로 하는 일을 더 잘 이해하려면, 그 선언을 뺀 변형 파일 Bar.rakumod와 대조해보는 게 좋아요. 아래 서브루틴 정의는 거의 동일해요 (차이는 명확성을 위해 수정한 greeting의 본문뿐이에요).

sub greeting ($name = 'Camelia') { "Greetings from Bar, $name!" }
our sub loud-greeting (--> Str)  { greeting().uc                }
sub friendly-greeting is export  { greeting('friend')           }

다시 한번, 이전에 Foo.rakumod를 쓴 방식은 이랬어요.

use Foo;
say Foo::loud-greeting;  # OUTPUT: «GREETINGS, CAMELIA!␤»
say friendly-greeting;   # OUTPUT: «Greetings, friend!␤»

그리고 Bar.rakumod를 쓰는 방식은 이렇게 돼요.

use Bar;
say loud-greeting;       # OUTPUT: «GREETINGS FROM BAR, CAMELIA!␤»
say friendly-greeting;   # OUTPUT: «Greetings from Bar, friend!␤»

여기서 Bar::loud-greeting이 아니라 loud-greeting을 쓴 점을 주목하세요. Bar는 알려진 심볼이 아니기 때문이에요 (우리는 Bar.rakumod에 그런 이름의 모듈을 만들지 않았어요). 그런데 왜 export로 표시하지도 않은 loud-greeting을 호출할 수 있는 걸까요? 그 답은 간단해요. Bar.rakumod는 새로운 패키지 네임스페이스를 만들지 않아서, $?PACKAGE가 여전히 GLOBAL로 남아 있어요. 그래서 loud-greetingour로 선언하면 그 이름이 GLOBAL 심볼 테이블에 등록되는 거예요.

어휘 별칭과 안전성 (Lexical aliasing and safety)

다행히 Raku는 호출 지점에 있는 정의(내장 함수 같은 것)를 실수로 덮어쓰는 일로부터 우리를 보호해줘요. Bar.rakumod에 아래 내용을 추가해볼게요.

our sub say ($ignored) { print "oh dear\n" }

이것은 **어휘 별칭(lexical alias)**을 만들어서, Bar.rakumod 안에서만 say 내장 함수를 숨겨요. 호출하는 쪽의 say는 그대로 남아 있어요. 그래서 아래처럼 say를 호출해도 여전히 기대한 대로 동작해요.

use Bar;
say 'Carry on, carry on...';  # OUTPUT: «Carry on, carry on...␤»

모듈에 관한 문서들 (Documentation about Modules)

모듈을 배포하고 싶나요? 그렇다면 아래 문서들을 참고하세요.