모듈: 코드에서 사용하기
모듈: 코드에서 사용하기 (using-modules_code)
Raku 코드 안에서 모듈을 어떻게 사용하는지 다루는 문서예요.
로드와 기본 가져오기
모듈을 로드하면 같은 네임스페이스 안에 선언된 패키지들이 로더의 파일 스코프에서 사용 가능해져요. 모듈에서 가져오기(import)하면 내보낸 심볼들이 가져오는 문의 어휘 스코프에서 사용 가능해져요.
need
need는 컴파일 타임에 compunit을 로드해요(기술적 상세는 여기 참고).
need MyModule;
네임스페이스 안에 정의된 어떤 패키지든 사용 가능해져요.
# MyModule.rakumod
unit module MyModule;
class Class {}
MyModule이 로드되면 MyModule::Class가 정의되고, 완전 정규화된 이름(FQN)으로 바로 사용할 수 있어요. 이렇게 정의된 클래스와 다른 타입은 자동으로 export되지 않아요. 짧은 이름으로 쓰고 싶다면 명시적으로 export해야 해요:
# MyModule.rakumod
unit module MyModule;
class Class is export {}
그리고 나서:
use MyModule;
my $class = Class.new();
say $class.raku;
use
use는 컴파일 타임에 compunit을 로드한 뒤 그로부터 가져와요. .rakumod로 끝나는 파일을 찾아요. 런타임이 모듈을 어디서 찾는지는 여기를 참고하세요.
use MyModule;
이것은 다음 코드와 동등해요:
need MyModule;
import MyModule;
가져올 것을 제한하려면 선택적 가져오기(selective importing)도 참고하세요.
require
require는 런타임에 compunit을 로드하고 그 네임스페이스의 내용을 접근 가능하게 해요. 즉, 확정 심볼(definite symbol, :D이고 :U가 아닌 것)을 완전 정규화된 이름으로 접근할 수 있어요. 런타임에 어휘 스코프를 늘릴 수 없기 때문에 FQN이 필요해요.
say "loading MyModule";
require MyModule;
compunit 이름은 간접 조회(indirect lookup) 안에 넣으면 런타임 변수일 수 있어요.
my $name = 'MyModule';
require ::($name);
로드된 모듈이 제공하는 심볼은 현재 어휘 스코프로 가져와지지 않아요. 심볼의 완전 정규화된 이름을 제공해 동적 조회(dynamic lookup)나 동적 서브셋을 써서 사용할 수 있어요. 예:
require ::("Test");
my &mmk = ::("Test::EXPORT::DEFAULT::&ok");
mmk('oi‽'); # OUTPUT: «ok 1 - »
ok의 FQN은 Test::EXPORT::DEFAULT::&ok예요. 이를 mmk에 별칭을 붙여서 현재 스코프에서 Test가 제공하는 그 심볼을 쓸 수 있게 했어요.
심볼을 가져오려면 컴파일 타임에 정의해야 해요. 참고: require는 어휘 스코프예요:
sub do-something {
require MyModule <&something>;
say ::('MyModule'); # MyModule 심볼이 여기에는 존재
something() # &something이 여기에 정의됨
}
say ::('MyModule'); # 여기에는 MyModule 심볼이 없음
do-something();
# &something은 여기에 정의되지 않음
MyModule이 &something을 export하지 않으면 require는 실패해요.
컴파일 타임 심볼을 가진 require는 로드된 모듈·클래스·패키지로 갱신될 placeholder package를 설치해요. 참고로 require가 모듈 로드에 실패해도 placeholder는 유지돼요. 즉, 이렇게 모듈이 로드됐는지 확인하면 틀려요:
# *** 틀림: ***
try require Foo;
if ::('Foo') ~~ Failure { say "Failed to load Foo!"; }
# *** 틀림: ***
컴파일 타임에 설치된 패키지 때문에 ::('Foo')가 Failure가 되는 일이 없어요. 올바른 방법은:
# 로드 성공 여부를 반환값으로 확인:
(try require Foo) === Nil and say "Failed to load Foo!";
# 또는 require로 런타임 심볼 조회를 써서 컴파일 타임의
# 패키지 설치를 피하기:
try require ::('Foo');
if ::('Foo') ~~ Failure {
say "Failed to load Foo!";
}
언어의 현재(6.d) 버전에서 require된 심볼은 더 이상 전이적으로 노출되지 않아요. 즉 심볼을 원래 선언한 모듈에서 가져와야지, 그 모듈이 심볼을 다시 가져온 곳이 아니라.
어휘 모듈 로드 (Lexical module loading)
Raku는 전역 상태를 피하는 데 신경을 많이 써요. 모듈에서 무슨 일을 하든 다른 코드에 영향을 주면 안 된다는 뜻이에요. 그래서 서브루틴 정의가 기본적으로 어휘(my) 스코프인 거죠. 다른 사람이 보게 하려면 명시적으로 our 스코프로 만들거나 export해야 해요.
클래스는 로드된 모듈에 들어있는 클래스에 접근할 수 없다면 그 모듈이 별로 쓸모없을 거라는 가정하에 기본적으로 export돼요. 그래서 로드된 클래스는 처음 로드한 스코프에서만 등록돼요[1]. 이 말은 실제로 클래스를 쓰는 모든 스코프에서 그 클래스를 use해야 한다는 뜻이에요.
use Foo; # Foo는 어딘가에 "use Bar"를 갖고 있음
use Bar;
my $foo = Foo.new;
my $bar = Bar.new;
export와 선택적 가져오기
is export
패키지, 서브루틴, 변수, 상수, enum은 is export 트레이트로 표시해 export할 수 있어요(작성자·버전 표시용 태그도 참고하세요).
unit module MyModule:ver<1.0.3>:auth<John Hancock ([email protected])>;
our $var is export = 3;
sub foo is export { ... };
constant FOO is export = "foobar";
enum FooBar is export <one two three>;
# multi 메서드의 경우, proto를 선언했다면
# proto만 is export로 표시하면 됨
proto quux(Str $x, |) is export { * };
multi quux(Str $x) { ... };
multi quux(Str $x, $y) { ... };
# multi 메서드의 경우, 하나만 is export로 표시해도 되지만
# 모두 표시하는 게 가장 일관적
multi quux(Str $x) is export { ... };
multi quux(Str $x, $y) is export { ... };
# 클래스 같은 패키지도 export할 수 있음
class MyClass is export {};
# 서브패키지가 현재 패키지의 네임스페이스에 있으면
# 명시적으로 export할 필요 없음
class MyModule::MyClass {};
다른 트레이트와 마찬가지로 루틴에 적용하면 is export는 인자 목록 뒤에 와야 해요.
sub foo(Str $string) is export { ... }
is export에 이름 있는 인자를 넘겨 심볼을 그룹으로 묶어서, 가져오는 쪽이 고르게 할 수 있어요. 미리 정의된 태그는 세 가지예요: ALL, DEFAULT, MANDATORY.
# lib/MyModule.rakumod
unit module MyModule;
sub bag is export { ... }
# ':MANDATORY' 태그가 붙은 객체는 항상 export됨
sub pants is export(:MANDATORY) { ... }
sub sunglasses is export(:day) { ... }
sub torch is export(:night) { ... }
sub underpants is export(:ALL) { ... }
# main.raku
use lib 'lib';
use MyModule; # bag, pants
use MyModule :DEFAULT; # 동일
use MyModule :day; # pants, sunglasses
use MyModule :night; # pants, torch
use MyModule :ALL; # bag, pants, sunglasses, torch, underpants
참고: 현재로서는 모듈 작성자가 그런 장치를 만들어두지 않았다면 사용자가 단일 객체를 가져올 방법이 없고, 지금은 쉬운 일도 아니에요(RT #127305 참고). 작성자가 그런 접근을 제공하는 한 방법은 각 export 트레이트에 고유한 태그를 주는 거예요. (태그가 객체 이름일 수도 있어요!) 그러면 사용자는 (1) 모든 객체를 가져오거나:
use Foo :ALL;
(2) 하나 이상의 객체를 선택적으로 가져올 수 있어요:
use Foo :bar, :s5;
참고 사항:
-
export된 sub의
:MANDATORY태그는 사용하는 프로그램이 어떤 태그를 붙이든 무조건 export되게 해요. -
명시적 태그가 없는 export된 모든 sub는 암묵적으로
:DEFAULT예요. -
모듈 이름과 태그 사이의 공백은 필수예요.
-
가져오기 태그는 여러 개(쉼표로 구분) 쓸 수 있어요. 예:
# main.raku
use lib 'lib';
use MyModule :day, :night; # pants, sunglasses, torch
export트레이트에도 여러 태그를 쓸 수 있지만 모두 쉼표나 공백으로 구분해야 해요.
sub foo() is export(:foo :s2 :net) {}
sub bar() is export(:bar, :s3, :some) {}
UNIT::EXPORT::*
겉으로는 is export가 EXPORT 네임스페이스의 UNIT 스코프 패키지에 심볼을 추가하는 방식이에요. 예를 들어 is export(:FOO)는 대상을 UNIT::EXPORT::FOO 패키지에 추가해요. 이것이 Raku가 실제로 무엇을 가져올지 결정하는 데 쓰는 방식이에요.
unit module MyModule;
sub foo is export { ... }
sub bar is export(:other) { ... }
이것은 다음과 같아요:
unit module MyModule;
my package EXPORT::DEFAULT {
our sub foo { ... }
}
my package EXPORT::other {
our sub bar { ... }
}
대부분의 경우 is export로 충분하지만, 내보낼 심볼을 동적으로 만들고 싶다면 EXPORT 패키지가 유용해요. 예:
# lib/MyModule.rakumod
unit module MyModule;
my package EXPORT::DEFAULT {
for <zero one two three four>.kv -> $number, $name {
for <sqrt log> -> $func {
OUR::{'&' ~ $func ~ '-of-' ~ $name } := sub { $number."$func"() };
}
}
}
# main.raku
use MyModule;
say sqrt-of-four; # OUTPUT: «2»
say log-of-zero; # OUTPUT: «-Inf»
EXPORT
EXPORT sub로 임의의 심볼을 export할 수 있어요. EXPORT는 Map을 반환해야 하는데, 키는 심볼 이름이고 값은 원하는 값이에요. 이름에는 관련 타입의 sigil(있으면)이 포함돼야 해요.
# lib/MyModule.rakumod
class MyModule::Class { }
sub EXPORT {
Map.new:
'$var' => 'one',
'@array' => <one two three>,
'%hash' => %( one => 'two', three => 'four' ),
'&doit' => sub { say 'Greetings from exported sub' },
'ShortName' => MyModule::Class
}
이 메인 파일에서 사용하게 되는 코드예요:
# main.raku
use lib 'lib';
use MyModule;
say $var; # OUTPUT: «one»
say @array; # OUTPUT: «(one two three)»
say %hash; # OUTPUT: «{one => two, three => four}»
doit(); # OUTPUT: «Greetings from exported sub»
say ShortName.new; # OUTPUT: «MyModule::Class.new»
EXPORT는 compunit의 일부이지 패키지의 일부가 아니기 때문에 패키지 안에 선언할 수 없어요.
UNIT::EXPORT 패키지가 use에 전달되는 이름 있는 인자를 처리하는 반면, EXPORT sub는 위치 인자를 처리해요. use에 위치 인자를 넘기면 그 인자들이 EXPORT로 전달돼요. 위치 인자가 넘겨지면 모듈은 더 이상 기본 심볼을 export하지 않아요. 그래도 :DEFAULT를 위치 인자와 함께 use에 넘겨 명시적으로 가져올 수는 있어요.
# lib/MyModule
class MyModule::Class {}
sub EXPORT($short_name?) {
Map.new: do $short_name => MyModule::Class if $short_name
}
sub always is export(:MANDATORY) { say "works" }
#import with :ALL or :DEFAULT to get
sub shy is export { say "you found me!" }
이 메인 프로그램에서 쓰는 예시에요:
# main.raku
use lib 'lib';
use MyModule 'foo';
say foo.new(); # OUTPUT: «MyModule::Class.new»
always(); # OK - 가져와짐
shy(); # FAIL - «shy used at line 8. Did you mean 'say'?»
EXPORT를 타입 캡처와 결합하면 재미있는 효과를 낼 수 있어요. 이 예시는 Cool에서만 동작하는 ? 후위 연산자를 만드는데, $_를 인자로 써서 루틴 본문에서 변수를 쓰지 않고 주제 변수(topic variable)에 기본 적용되는 .so만 쓰면 돼요.
# lib/MakeQuestionable.rakumod
sub EXPORT(::Questionable) {
my multi postfix:<?>(Questionable $_) { .so };
Map.new: '&postfix:<?>' => &postfix:<?>,
}
이렇게 사용해요:
use lib 'lib';
use MakeQuestionable Cool;
say ( 0?, 1?, {}?, %( a => "b" )? ).join(' '); # OUTPUT: «False True False True»
sub EXPORT를 unit 선언자 뒤에 두지 않도록 주의하세요. 그렇게 하면 특별한 export sub가 아니라 패키지 안의 일반 sub가 돼버려요:
unit module Bar;
sub EXPORT { Map.new: Foo => &say } # 틀림!!! Sub 스코프가 잘못됨
정의에서 설명했듯 sub EXPORT는 패키지가 아니라 compunit의 일부예요. 그러니 이게 올바른 방법이에요:
sub EXPORT { Map.new: Foo => &say } # 맞음!!! Sub가 모듈 밖에 있음
unit module Bar;
인트로스펙션 (Introspection)
모듈의 export된 심볼을 나열하려면 먼저 그 모듈이 지원하는 export 태그를 조회해요.
use URI::Escape;
say URI::Escape::EXPORT::.keys;
# OUTPUT: «(DEFAULT ALL)»
그다음 원하는 태그를 써서 심볼을 이름으로 골라요.
say URI::Escape::EXPORT::DEFAULT::.keys;
# OUTPUT: «(&uri-escape &uri-unescape &uri_escape &uri_unescape)»
my &escape-uri = URI::Escape::EXPORT::DEFAULT::<&uri_escape>;
모듈 관련 문서
- Using Modules: An Introduction
- Using Modules: Finding and Installing
- Using Modules: The Code (이 페이지)
- Making Modules: Introduction
- Making Modules: The Code
모듈을 배포하고 싶다면:
- Distributions: An introduction
- Distributions: The Configuration and Structure
- Distributions: The Tools
- Distributions: Testing
- Distributions: Uploading
[1] 이 변경은 2016년 후반에 도입됐어요. 그보다 오래된 버전을 쓰면 동작이 달라요.