독립 루틴

독립 루틴 (Independent routines)

어떤 클래스나 롤에도 속하지 않은 채 독립적으로 정의되는 루틴들이 있어요. 이 루틴들은 다른 클래스들과 함께 서로 다른 파일에 정의되지만, 특정 클래스나 롤에 붙어 있지는 않아요. 이름만 떼어 불러도 되는 이런 루틴들을 한자리에 모아봤어요.

출처: Raku Documentation — Independent routines

본문

routine EVAL

proto EVAL($code where Blob|Cool|Callable, Str() :$lang = 'Raku',
                PseudoStash :$context, Str() :$filename, Bool() :$check, *%_)
multi EVAL($code, Str :$lang where { ($lang // '') eq 'Perl5' },
                PseudoStash :$context, Str() :$filename, :$check)

이 루틴은 런타임에 주어진 언어 $lang의 코드 조각 $code를 실행해요. $lang의 기본값은 Raku예요.

Cool$codeStr로 강제 변환돼요. $codeBlob이면 $lang 컴파일러가 사용하는 것과 같은 인코딩으로 처리돼요. Raku $lang에는 utf-8을, Perl5에는 Perl과 같은 규칙으로 처리해요.

이것은 문자열 리터럴 매개변수와 그대로 동작해요. 변수나 코드가 내장된 문자열 같은 더 복잡한 입력은 기본적으로 불법이에요. 이는 여러 방법 중 하나로 재정의할 수 있어요.

use MONKEY-SEE-NO-EVAL; # Or...
use MONKEY;             # shortcut that turns on all MONKEY pragmas, or...
use Test;               # a module that activates MONKEY-SEE-NO-EVAL.

my $init = 0;
my $diff = 10;
my Str $changer = '$init += ' ~ $diff; # contains a Str object with value '$init += 10'
# any of the above allows:
EVAL $changer;
EVAL $changer;
say $init;                         # OUTPUT: «20␤»

MONKEY-SEE-NO-EVAL 프래그마가 활성화되어 있지 않으면 컴파일러는 EVAL is a very dangerous function!!!라고 불평해요. 그리고 본질적으로 맞는 말이에요. 프로그램과 같은 권한으로 임의의 코드를 실행하니까요. MONKEY-SEE-NO-EVAL 프래그마를 활성화했다면 EVAL로 통과시킬 코드를 정리하는 데 주의를 기울여야 해요.

따옴표를 써서 루틴 이름을 만들어 내삽(interpolate)할 수 있다는 점도 기억하세요. 다만 이것은 이미 선언된 함수와 다른 객체에 대해서만 동작하므로 더 안전해요.

현재 어휘 범위(lexical scope)의 심볼은 EVAL 안의 코드에 보여요.

my $answer = 42;
EVAL 'say $answer;';    # OUTPUT: «42␤»

다만 어휘 범위의 심볼 집합은 컴파일 타임 이후 불변이므로, EVAL은 결코 주변 범위에 심볼을 도입할 수 없어요.

EVAL 'my $lives = 9'; say $lives;   # error, $lives not declared

게다가 EVAL은 현재 패키지에서 평가돼요.

module M {
    EVAL 'our $answer = 42'
}
say $M::answer;         # OUTPUT: «42␤»

또한 현재 언어에서 평가되어, 추가된 문법이 그대로 사용 가능해요.

sub infix:<mean>(*@a) is assoc<list> {
    @a.sum / @a.elems
}
EVAL 'say 2 mean 6 mean 4';     # OUTPUT: «4␤»

EVAL 문은 마지막 문장의 결과로 평가돼요.

sub infix:<mean>(*@a) is assoc<list> {
    @a.sum / @a.elems
}
say EVAL 'say 1; 2 mean 6 mean 4';         # OUTPUT: «1␤4␤»

EVAL은 다른 언어의 코드를 실행하는 관문(gateway)이기도 해요.

EVAL "use v5.20; say 'Hello from perl!'", :lang<Perl5>;

이것이 올바르게 동작하려면 Inline::Perl5가 필요해요. 더 많은 언어는 Raku Modules Directory에서 찾을 수 있는 추가 모듈로 지원될 수 있어요.

선택적 $filename 매개변수가 주어지면 $?FILE 변수가 그 값으로 설정돼요. 그렇지 않으면 $?FILE은 고유하고 생성된 파일 이름으로 설정돼요.

use MONKEY-SEE-NO-EVAL;
EVAL 'say $?FILE';                              # OUTPUT: «/tmp/EVAL_0␤»
EVAL 'say $?FILE', filename => '/my-eval-code'; # OUTPUT: «/my-eval-code␤»

선택적 $check 매개변수가 True이면 $code$lang 컴파일러로 처리되지만 실제로 실행되지는 않아요. Raku에서는 BEGINCHECK 블록이 실행돼요. 그러면 EVAL 루틴은 컴파일이 성공했으면 Nil을, 아니면 예외를 반환해요.

sub EVALFILE

sub EVALFILE($filename where Blob|Cool, :$lang = 'Raku', :$check)

지정된 파일을 slurp해 평가해요. Blob 디코딩, 범위, $lang 매개변수, $check 매개변수와 관련해 EVAL과 같은 방식으로 동작해요. $checkTrue가 아니면 파일의 마지막 문장이 만들어내는 값으로 평가돼요.

EVALFILE "foo.raku";

sub repl

참고: repl은 Rakudo 컴파일러 2021.06 릴리스에 도입됐어요.

sub repl()

실행을 멈추고 현재 컨텍스트에서 REPL(읽기-평가-출력 루프)에 들어가요. 이 REPL은 raku를 인자 없이 실행할 때 만들어지는 것과 정확히 같지만, 프로그램의 현재 컨텍스트(예: 어휘 변수)에 접근하고 수정할 수 있다는 점이 달라요.

예를 들어 이 코드를 실행하면,

my $name = "Alice";

say "Hello, $name";

repl();

say "Goodbye, $name"

Hello, Alice 출력이 나오고 나서("goodbye" 출력이 찍히기 전에) REPL 세션에 들어가요. REPL 세션은 이렇게 진행될 수 있어요.

Type 'exit' to leave
[0] > $name
Alice
[1] > $name = "Bob"
Bob
[2] > exit

REPL 세션을 나오면 Raku가 프로그램 실행을 재개해요. 이 실행 동안 REPL에서 했던 변경은 여전히 유효해요. 그래서 위 세션 이후에는 REPL이 없었다면 나왔을 Goodbye, Alice가 아니라 Goodbye, Bob 출력이 나와요.

sub get

multi get  (IO::Handle:D $fh = $*ARGFILES) { $fh.get  }

이 루틴은 IO::Handle의 같은 이름의 메서드에 대한 래퍼예요. Handle이 지정되지 않으면 기본적으로 $*ARGFILES를 사용해요.

sub getc

multi getc  (IO::Handle:D $fh = $*ARGFILES) { $fh.getc  }

이 루틴은 IO::Handle의 같은 이름의 메서드에 대한 래퍼예요. Handle이 지정되지 않으면 기본적으로 $*ARGFILES를 사용해요.

sub mkdir

sub    mkdir(IO() $path, Int() $mode = 0o777 --> IO::Path:D)

새 디렉터리를 만들어요. $mode에 대한 설명과 유효한 값은 mode를 참고하세요. 성공하면 새로 만든 디렉터리를 가리키는 IO::Path 객체를 반환하고, 디렉터리를 만들 수 없으면 X::IO::Mkdirfail해요.

필요하면 부모 디렉터리도 만들어요(*nix 유틸리티 mkdir-p 옵션과 비슷해요). 즉 mkdir "foo/bar/ber/meow"는 존재하지 않으면 foo, foo/bar, foo/bar/ber 디렉터리와 foo/bar/ber/meow까지 만들어요.

sub chdir

sub chdir(IO() $path, :$d = True, :$r, :$w, :$x --> IO::Path:D)

$*CWD 변수의 값을 제공된 $path로 바꿔요. 선택적으로 새 경로가 몇 가지 파일 테스트를 통과하는지 확인해요. 참고: 이 루틴은 프로세스의 현재 디렉터리를 바꾸지 않아요(&*chdir 참고).

성공하면 새 $*CWD를 나타내는 IO::Path를 반환해요. 실패하면 Failure를 반환하고 $*CWD는 그대로 두어요. $pathIO::Path 객체를 반환하는 IO 메서드를 가진 어떤 객체도 될 수 있어요. 사용 가능한 파일 테스트는 다음과 같아요.

  • :d.dTrue를 반환하는지 검사
  • :r.rTrue를 반환하는지 검사
  • :w.wTrue를 반환하는지 검사
  • :x.xTrue를 반환하는지 검사

기본적으로는 :d 테스트만 수행돼요.

chdir         '/tmp'; # change $*CWD to '/tmp' and check its .d is True
chdir :r, :w, '/tmp'; # … check its .r and .w are True
chdir '/not-there';   # returns Failure

다음 구성은 실수라는 점을 기억하세요.

# WRONG! DO NOT DO THIS!
my $*CWD = chdir '/tmp/';

대신 indir를 사용해요.

sub &*chdir

PROCESS::<&chdir> = sub (IO() $path --> IO::Path:D) { }

$*CWD 변수의 값을 제공된 $path로 바꾸고, 프로세스의 현재 디렉터리를 $path.absolute의 값으로 설정해요. 참고: 대부분의 경우 chdir 루틴을 쓰는 게 좋아요.

성공하면 새 $*CWD를 나타내는 IO::Path를 반환해요. 실패하면 Failure를 반환하고 $*CWD는 그대로 두어요. $pathIO::Path 객체를 반환하는 IO 메서드를 가진 어떤 객체도 될 수 있어요.

일반 chdir와 달리 어떤 파일 테스트를 수행할지 지정하는 인자가 없다는 점에 주목하세요.

&*chdir('/tmp');  # change $*CWD and process's current directory to '/tmp'
&*chdir('/not-there'); # returns Failure

다음 구성은 실수라는 점을 기억하세요.

# WRONG! DO NOT DO THIS!
my $*CWD = &*chdir('/tmp');

대신 다음을 사용하거나, 프로세스의 현재 디렉터리를 바꿀 필요가 없다면 indir를 보세요.

temp $*CWD;
&*chdir('/tmp');

sub chmod

sub chmod(Int() $mode, *@filenames --> List)

모든 @filenamesIO::Path로 강제 변환하고, 그 대상에 $modeIO::Path.chmod를 호출해요. chmod가 성공적으로 실행된 @filenames의 부분집합을 담은 List를 반환해요.

chmod 0o755, <myfile1  myfile2>; # make two files executable by the owner

sub indir

sub indir(IO() $path, &code, :$d = True, :$r, :$w, :$x)

Callable &code를 받아, &code에 지역적으로 $*CWD 변수를 $path에 기반한 IO::Path 객체로 바꾼 뒤 실행해요. 선택적으로 새 경로가 몇 가지 파일 테스트를 통과하는지 확인해요. $path가 상대 경로면, IO::Path 객체가 주어졌어도 절대 경로로 바뀌어요. 참고: 이 루틴은 프로세스의 현재 디렉터리를 바꾸지 않아요(&*chdir 참고). &code 밖의 $*CWD는, &code$*CWD에 명시적으로 새 값을 할당해도 영향받지 않아요.

성공하면 &code 호출이 반환한 값을 반환해요. $*CWD를 성공적으로 바꾸지 못하면 Failure를 반환해요. 경고: 느긋하게(lazily) 평가되는 것들은 실제로 평가되는 시점에 indir가 설정한 $*CWD가 그들의 동적 범위에 없을 수 있음을 기억하세요. 생성기가 $*CWD를 갖도록 하거나, indir에서 결과를 반환하기 전에 이를 eager하게 평가하도록 하세요.

say indir("/tmp", {
    gather { take ".".IO }
})».CWD; # OUTPUT: «(/home/camelia)␤»

say indir("/tmp", {
    eager gather { take ".".IO }
})».CWD; # OUTPUT: «(/tmp)␤»

say indir("/tmp", {
    my $cwd = $*CWD;
    gather { temp $*CWD = $cwd; take ".".IO }
})».CWD; # OUTPUT: «(/tmp)␤»

루틴의 $path 인자는 IO::Path 객체를 반환하는 IO 메서드를 가진 어떤 객체도 될 수 있어요. 사용 가능한 파일 테스트는 다음과 같아요.

  • :d.dTrue를 반환하는지 검사
  • :r.rTrue를 반환하는지 검사
  • :w.wTrue를 반환하는지 검사
  • :x.xTrue를 반환하는지 검사

기본적으로는 :d 테스트만 수행돼요.

say $*CWD;                   # OUTPUT: «"/home/camelia".IO␤»
indir '/tmp', { say $*CWD }; # OUTPUT: «"/tmp".IO␤»
say $*CWD;                   # OUTPUT: «"/home/camelia".IO␤»

indir '/not-there', {;};     # returns Failure; path does not exist

sub print

multi print(**@args --> True)
multi print(Junction:D --> True)

주어진 텍스트를 표준 출력($*OUT 파일핸들)에 출력해요. Str이 아닌 객체는 .Str 메서드를 호출해 Str로 강제 변환해요. Junction 인자는 autothread되고 출력 문자열의 순서는 보장되지 않아요.

print "Hi there!\n";       # OUTPUT: «Hi there!␤»
print "Hi there!";         # OUTPUT: «Hi there!»
print [1, 2, 3];           # OUTPUT: «1 2 3»
print "Hello" | "Goodbye"; # OUTPUT: «HelloGoodbye»

텍스트를 출력하고 뒤따르는 새 줄을 포함하려면 put을 사용해요.

sub put

multi put()
multi put(**@args --> True)
multi put(Junction:D --> True)
multi put(Str:D \x)
multi put(\x)

print와 같지만, 끝에 print-nl(기본적으로 새 줄을 출력)을 사용해요. Junction 인자는 autothread되고 출력 문자열의 순서는 보장되지 않아요.

put "Hi there!\n";   # OUTPUT: «Hi there!␤␤»
put "Hi there!";     # OUTPUT: «Hi there!␤»
put [1, 2, 3];       # OUTPUT: «1 2 3␤»
put "Hello" | "Goodbye"; # OUTPUT: «Hello␤Goodbye␤»

단독으로 put()은 새 줄 하나를 출력해요.

put "Hey"; put(); put("Hey"); # OUTPUT: «Hey␤␤Hey␤»

다만 put 뒤에 괄호를 썼다는 점을 기억하세요. 괄호가 없으면(6.d 버전 이후) 예외를 던져요. for 앞에 그 방식으로 쓰면 예외도 발생하는데, 그 경우에는 메서드 형태 .put을 사용해요.

.put for <1 2 3>;             # OUTPUT: «1␤2␤3␤»

sub say

multi say(**@args --> True)

주어진 객체의 "gist"를 출력해요. 객체가 Str의 서브클래스인 경우에는 항상 .gist를 호출해요. put과 같지만, 객체의 문자열 표현을 얻는 데 .gist 메서드를 사용한다는 점이 달라요. put의 경우처럼 Junction에 대해서도 autothread해요.

참고: List 같은 일부 객체의 .gist 메서드는 객체에 대한 부분적인 정보만 반환해요(그래서 "gist"인 거죠). 텍스트 정보를 출력하려고 한다면 아마 put을 쓰는 게 더 좋을 거예요.

say Range;        # OUTPUT: «(Range)␤»
say class Foo {}; # OUTPUT: «(Foo)␤»
say 'I ♥ Raku';   # OUTPUT: «I ♥ Raku␤»
say 1..Inf;       # OUTPUT: «1..Inf␤»

routine note

method note(Mu: -->Bool:D)
multi  note(            --> Bool:D)
multi  note(Str:D $note --> Bool:D)
multi  note(**@args     --> Bool:D)

say와 같아요(출력 객체의 .gist 메서드를 호출한다는 점에서). 다만 출력을 $*ERR 핸들(STDERR)로 보내요. 서브루틴 형태에 인자를 주지 않으면 문자열 "Noted"를 사용해요.

note;       # STDERR OUTPUT: «Noted␤»
note 'foo'; # STDERR OUTPUT: «foo␤»
note 1..*;  # STDERR OUTPUT: «1..Inf␤»

이 명령은 Junction에서도 autothread하며, 객체가 Str의 서브클래스면 그 객체에 gist를 호출하도록 보장돼요.

sub prompt

multi prompt()
multi prompt($msg)

$msg가 제공되면 $msg$*OUT 핸들로 print하고, 그다음 $*IN 핸들에서 입력 한 줄을 get해요. 기본적으로는 $msg를 STDOUT에 출력하고, STDIN에서 한 줄을 읽고, 뒤따르는 새 줄을 제거한 뒤 결과 문자열을 반환하는 것과 동일해요. Rakudo 2018.08부터 prompt는 숫자 값에 대해 allomorph를 만들어요. 이는 val prompt를 호출하는 것과 동일해요.

my $name = prompt "What's your name? ";
say "Hi, $name! Nice to meet you!";
my $age = prompt("Say your age (number)");
my Int $years = $age;
my Str $age-badge = $age;

위 코드에서 $age는 숫자로 올바르게 입력되면 allomorph IntStr로 덕 타이핑(duck-typing)돼요.

sub open

multi open(IO() $path, |args --> IO::Handle:D)

주어진 $path로 핸들을 만들고, 나머지 인자를 전달하며 IO::Handle.open을 호출해요. IO::Path 타입은 파일에서 읽고 쓰는 수많은 메서드를 제공하므로, 많은 일반적인 경우에는 파일을 open하거나 IO::Handle 타입을 직접 다룰 필요가 없어요.

my $fh = open :w, '/tmp/some-file.txt';
$fh.say: 'I ♥ writing Raku code';
$fh.close;

$fh = open '/tmp/some-file.txt';
print $fh.readchars: 4;
$fh.seek: 7, SeekFromCurrent;
say $fh.readchars: 4;
$fh.close;

# OUTPUT: «I ♥ Raku␤»

sub slurp

multi slurp(IO::Handle:D $fh = $*ARGFILES, |c)
multi slurp(IO() $path, |c)

전체 파일의 내용을 Str(:bin이면 Buf)로 slurp해요. open()과 같은 의미의 선택적 명명 매개변수 :bin:enc를 받아요. 가능한 인코딩은 다른 모든 IO 메서드와 같으며 encoding 루틴에 나열돼 있어요. 파일이 존재하지 않거나 디렉터리면 fail해요. 인자 없이 slurp 서브를 호출하면 $*ARGFILES에 대해 동작하는데, 파일 이름이 없으면 기본값 $*IN이 돼요.

# read entire file as (Unicode) Str
my $text_contents   = slurp "path/to/file";

# read entire file as Latin1 Str
my $text_contents   = slurp "path/to/file", enc => "latin1";

# read entire file as Buf
my $binary_contents = slurp "path/to/file", :bin;

sub spurt

multi spurt(IO() $path, |c)

$pathIO::Path 객체를 반환하는 IO 메서드를 가진 어떤 객체도 될 수 있어요. $pathIO::Path.spurt를 호출하며 나머지 인자를 전달해요.

옵션 (Options)

  • :enc — 내용이 쓰여질 인코딩.
  • :append — (있을 수 있는) 기존 파일에 덧붙일지 여부를 나타내는 불리언. 파일이 아직 없었다면 생성돼요. 기본값은 False.
  • :createonly — 파일이 이미 존재하면 실패할지 여부를 나타내는 불리언. 기본값은 False.

예시 (Examples)

# write directly to a file
spurt 'path/to/file', 'default text, directly written';

# write directly with a non-Unicode encoding
spurt 'path/to/latin1_file', 'latin1 text: äöüß', :enc<latin1>;

spurt 'file-that-already-exists', 'some text';           # overwrite file's contents:
spurt 'file-that-already-exists', ' new text', :append;  # append to file's contents:
say slurp 'file-that-already-exists';                    # OUTPUT: «some text new text␤»

# fail when writing to a pre-existing file
spurt 'file-that-already-exists', 'new text', :createonly;
# OUTPUT: «Failed to open file /home/camelia/file-that-already-exists: file already exists …»
multi spurt(IO() $path)

Rakudo 컴파일러 2020.12 릴리스부터는 데이터 없이 spurt 서브루틴을 호출할 수도 있어요. 그러면 빈 파일을 만들거나, 주어진 경로의 기존 파일을 truncate해요.

# create an empty file / truncate a file
spurt 'path/to/file';

sub run

sub run(
    *@args ($, *@),
    :$in = '-',
    :$out = '-',
    :$err = '-',
    Bool :$bin = False,
    Bool :$chomp = True,
    Bool :$merge = False,
    Str:D :$enc = 'UTF-8',
    Str:D :$nl = "\n",
    :$cwd = $*CWD,
    Hash() :$env = %*ENV,
    :$arg0,
    :$win-verbatim-args = False
--> Proc:D)

쉘을 거치지 않고 외부 명령을 실행하고 Proc 객체를 반환해요. 기본적으로 외부 명령은 표준 출력과 오류에 출력하고 표준 입력에서 읽어요.

run 'touch', '--', '*.txt'; # Create a file named “*.txt”

run <rm -- *.txt>; # Another way to use run, using word quoting for the
                   # arguments

변수를 넘기고 싶다면 여전히 <>를 쓸 수 있지만, 변수 따옴표를 빼먹으면 단어 분리(word splitting)를 하므로 « »는 피하는 게 좋아요.

my $file = ‘--my arbitrary filename’;
run ‘touch’, ‘--’, $file;  # RIGHT
run <touch -->, $file;     # RIGHT

run «touch -- "$file"»;    # RIGHT but WRONG if you forget quotes
run «touch -- $file»;      # WRONG; touches ‘--my’, ‘arbitrary’ and ‘filename’
run ‘touch’, $file;        # WRONG; error from `touch`
run «touch "$file"»;       # WRONG; error from `touch`

많은 프로그램이 명령줄 인자와 하이픈으로 시작하는 파일 이름을 구분하기 위해 -->가 필요하다는 점을 기억하세요.

실패하게 exit한 프로세스의 sink된 Proc 객체는 Exception을 던져요. 예외를 무시하고 내부를 검사할 수 있는 Proc를 반환받고 싶다면, sink가 아닌 컨텍스트에서 run을 사용해요.

run 'false';     # SUNK! Will throw an Exception
run('false').so; # OK. Evaluates Proc in Bool context; no sinking. Ignore returned Proc
my $a = run('false'); # Can call methods on the Proc in $a.

표준 출력이나 오류를 직접 출력하는 대신 캡처하고 싶다면 :out이나 :err 인자를 쓸 수 있어요. 이들은 각각의 메서드인 Proc.outProc.err로 사용할 수 있게 해줘요.

my $proc = run 'echo', 'Raku is Great!', :out, :err;
$proc.out.slurp(:close).say; # OUTPUT: «Raku is Great!␤»
$proc.err.slurp(:close).say; # OUTPUT: «␤»

이 인자들을 사용해 파이프의 일종을 만들어 파일핸들로 리다이렉트할 수도 있어요.

my $ls-alt-handle = open :w, '/tmp/cur-dir-ls-alt.txt';
my $proc = run "ls", "-alt", :out($ls-alt-handle);
# (The file will contain the output of the ls -alt command)

이 인자들은 꽤 유연해서, 예를 들어 리다이렉트할 핸들을 받아들여요. 자세한 내용은 ProcProc::Async를 참고하세요.

모든 인자에 대한 더 많은 예시와 설명은 newspawn도 참고하세요.

sub shell

multi shell($cmd, :$in = '-', :$out = '-', :$err = '-',
                Bool :$bin, Bool :$chomp = True, Bool :$merge,
                Str :$enc, Str:D :$nl = "\n", :$cwd = $*CWD, :$env)

시스템 쉘을 통해 명령을 실행해요. Windows에서는 기본값이 %*ENV<ComSpec> /c, 그 외에는 /bin/sh -c예요. 파이프, 리다이렉트, 환경 변수 치환 등 모든 쉘 메타문자는 쉘에 의해 해석돼요. 쉘 이스케이프는 심각한 보안 문제이며, 특이한 파일 이름과 혼란을 일으킬 수 있어요. 안전을 원한다면 run을 사용해요.

반환값은 Proc 타입이에요.

shell 'ls -lR | gzip -9 > ls-lR.gz';

출력 캡처 방법 등 자세한 내용은 Proc를 참고하세요.

routine unpolar

method unpolar(Real $angle)
multi  unpolar(Real $mag, Real $angle)

각도(라디안)에 대응하는 좌표와, 객체 값 또는 sub로 사용될 때 $mag에 대응하는 크기를 가진 Complex를 반환해요.

say 1.unpolar(⅓*pi);
# OUTPUT: «0.5000000000000001+0.8660254037844386i␤»

routine printf

multi printf(Cool:D $format, *@args)

형식에 따라 출력을 만들어내요. 사용되는 형식은 호출 객체(메서드 형태일 때) 또는 첫 번째 인자(루틴으로 호출될 때)예요. 나머지 인자는 형식 규약을 따라 형식에 치환돼요. 허용되는 형식 지시자에 대한 자세한 내용은 sprintf를 참고하세요.

"%s is %s".printf("þor", "mighty");    # OUTPUT: «þor is mighty»
printf( "%s is %s", "þor", "mighty");  # OUTPUT: «þor is mighty»

Junction에서는 순서 보장 없이 autothread해요.

printf( "%.2f ", ⅓ | ¼ | ¾ ); # OUTPUT: «0.33 0.25 0.75 »

routine sprintf

multi sprintf(Cool:D $format, *@args)

아래에 설명된 형식에 따라 문자열을 반환해요. 사용되는 형식은 호출 객체(메서드 형태일 때) 또는 첫 번째 인자(루틴으로 호출될 때)예요.

sprintf( "%s the %d%s", "þor", 1, "st").put; # OUTPUT: «þor the 1st␤»
sprintf( "%s is %s", "þor", "mighty").put;   # OUTPUT: «þor is mighty␤»
"%s's weight is %.2f %s".sprintf( "Mjölnir", 3.3392, "kg").put;
# OUTPUT: «Mjölnir's weight is 3.34 kg␤»

이 함수는 C 라이브러리의 sprintfprintf 함수와 대부분 동일해요. 두 함수의 유일한 차이는 sprintf는 문자열을 반환하는 반면 printf 함수는 파일핸들에 쓴다는 점이에요. sprintf는 리터럴이 아니라 Str을 반환해요.

$format% 문자에 대해 스캔돼요. 어떤 %든 형식 토큰을 도입해요. 지시자는 인자(있다면)의 사용을 안내해요. %가 아닌 지시자가 사용되면, 그다음에 전달될 인자가 만들어질 문자열에 어떻게 형식화될지 나타내요. 형식 토큰에는 매개변수 인덱스(parameter index)도 사용할 수 있어요. 이는 N$ 형태를 취하며 아래에 더 자세히 설명돼요.

$format은 작은따옴표나 큰따옴표로 둘러싸여 정의될 수 있어요. 큰따옴표로 묶인 $format 문자열은 스캔되기 전에 내삽(interpolate)되며, 내삽된 값에 % 문자가 포함된 내장 문자열은 예외를 일으켜요. 예를 들어:

my $prod = "Ab-%x-42";
my $cost = "30";
sprintf("Product $prod; cost: \$%d", $cost).put;
# OUTPUT: «Your printf-style directives specify 2 arguments, but 1 argument was supplied␤»
          «  in block <unit> at <unknown file> line 1␤»

알 수 없는 입력을 다룰 때는 모든 변수를 *@args 배열에 넣고 $format에 각각에 대해 하나씩 %를 두는 방식으로 이런 문법을 피해야 해요. 형식 문자열에 $ 기호를(매개변수 인덱스로라도) 포함해야 한다면 이스케이프하거나 작은따옴표 형태를 사용해요. 예를 들어 다음 두 형태 모두 오류 없이 동작해요.

sprintf("2 x \$20 = \$%d", 2*20).put; # OUTPUT: «2 x $20 = $40␤»
sprintf('2 x $20 = $%d', 2*20).put;   # OUTPUT: «2 x $20 = $40␤»

요약하면, 아주 특별한 것이 필요한 게 아니라면, 작은따옴표 형식 문자열을 쓰고 형식 문자열 안에 내삽 문자열을 사용하지 않는 편이 예상치 못한 문제가 더 적어요.

아래 정보는 아직 완전히 구현되지 않은 완전히 작동하는 sprintf 구현을 위한 것이에요. 아직 구현되지 않은 형식이나 기능은 NYI로 표시돼 있어요.

지시자 (Directives)

% 리터럴 퍼센트 기호
c 주어진 codepoint를 가진 문자
s 문자열
d 십진수, 부호 있는 정수
u 십진수, 부호 없는 정수
o 8진수, 부호 없는 정수
x 16진수, 부호 없는 정수
e 과학적 표기법의 부동소수점 수
f 고정 소수 표기법의 부동소수점 수
g %e 또는 %f 표기법의 부동소수점 수
X x와 같지만 대문자 사용
E e와 같지만 대문자 "E" 사용
G g와 같지만 (적용 시) 대문자 "E" 사용
b 이진수, 부호 없는 정수

호환성:

i %d의 동의어
D %ld의 동의어
U %lu의 동의어
O %lo의 동의어
F %f의 동의어

수정자 (Modifiers)

수정자는 형식 지시자의 의미를 바꾸지만, 대부분 no-op이에요(의미는 아직 결정 중).

h — 정수를 네이티브 "short"(보통 int16)로 해석
NYI l — 정수를 네이티브 "long"(보통 int32 또는 int64)로 해석
NYI ll — 정수를 네이티브 "long long"(보통 int64)로 해석
NYI L — 정수를 네이티브 "long long"(보통 uint64)로 해석
NYI q — 정수를 네이티브 "quads"(보통 int64 또는 그 이상)로 해석

%와 형식 문자 사이에는 형식 해석을 제어하는 여러 추가 속성을 지정할 수 있어요. 순서대로:

NYI '$' 기호를 사용한 형식 매개변수 인덱스

지시자 앞에 명시적 형식 매개변수 인덱스(1부터 N 인자까지), 예를 들어 %2$d가 있어요. 기본적으로 sprintf는 목록에서 다음 미사용 인자를 형식화하지만, 매개변수 인덱스를 쓰면 인자를 순서 없이 가져올 수 있어요(작은따옴표가 요구된다는 점을 기억하세요. $를 이스케이프하지 않는 한).

인덱스 없이:

sprintf '%d %d', 12, 34;      # OUTPUT: «12 34␤»
sprintf '%d %d %d', 1, 2, 3;  # OUTPUT: «1 2 3␤»

NYI 인덱스로:

첫 번째 예는 모든 지시자를 인덱싱할 때 우리가 기대하는 대로 동작해요.

sprintf '%2$d %1$d', 12, 34;      # OUTPUT: «34 12␤»

하지만 두 번째 예에서 인덱스된 지시자와 인덱스되지 않은 지시자를 섞을 때의 효과를 주목하세요(조심해서 요구하세요). 두 번째, 인덱스되지 않은 지시자는 첫 번째 인자를 받지만, 그것은 또한 마지막 지시자에서 명시적으로 요청되기도 해요.

sprintf '%3$d %d %1$d', 1, 2, 3;  # OUTPUT: «3 1 1␤»

플래그 (Flags)

하나 이상:

space 음이 아닌 수 앞에 공백을 붙임
+ 음이 아닌 수 앞에 더하기 기호를 붙임
- 필드 안에서 왼쪽 정렬
0 필요한 패딩에 공백 대신 앞쪽 0 사용
# 어떤 8진수에든 앞쪽 "0" 보장, 0이 아닌 16진수에 "0x"/"0X" 접두사, 0이 아닌 이진수에 "0b"/"0B" 접두사
v NYI 벡터 플래그(d 지시자에서만 사용), 아래 설명 참고

예를 들어:

sprintf '<% d>',  12;   # OUTPUT: «< 12>␤»
sprintf '<% d>',   0;   # OUTPUT: «< 0>"»
sprintf '<% d>', -12;   # OUTPUT: «<-12>␤»
sprintf '<%+d>',  12;   # OUTPUT: «<+12>␤»
sprintf '<%+d>',   0;   # OUTPUT: «<+0>"»
sprintf '<%+d>', -12;   # OUTPUT: «<-12>␤»
sprintf '<%6s>',  12;   # OUTPUT: «<    12>␤»
sprintf '<%-6s>', 12;   # OUTPUT: «<12    >␤»
sprintf '<%06s>', 12;   # OUTPUT: «<000012>␤»
sprintf '<%#o>',  12;   # OUTPUT: «<014>␤»
sprintf '<%#x>',  12;   # OUTPUT: «<0xc>␤»
sprintf '<%#X>',  12;   # OUTPUT: «<0XC>␤»
sprintf '<%#b>',  12;   # OUTPUT: «<0b1100>␤»
sprintf '<%#B>',  12;   # OUTPUT: «<0B1100>␤»

공백과 더하기 기호가 플래그로 동시에 주어지면 공백은 무시돼요.

sprintf '<%+ d>', 12;   # OUTPUT: «<+12>␤»
sprintf '<% +d>', 12;   # OUTPUT: «<+12>␤»

%o 변환에서 # 플래그와 정밀도(precision)가 주어지면 시작에 필요한 개수의 0이 추가돼요. 숫자 값이 0이고 정밀도가 0이면 아무것도 출력되지 않고, 정밀도가 0이거나 실제 요소 수보다 작으면 왼쪽에 0이 붙은 수가 반환돼요.

say sprintf '<%#.5o>', 0o12;     # OUTPUT: «<00012>␤»
say sprintf '<%#.5o>', 0o12345;  # OUTPUT: «<012345>␤»
say sprintf '<%#.0o>', 0;        # OUTPUT: «<>␤» zero precision and value 0
                                 #               results in no output!
say sprintf '<%#.0o>', 0o1       # OUTPUT: «<01>␤»

벡터 플래그 'v'

이 특별한 플래그(v, 그다음 지시자 d)는 Raku에게 주어진 문자열을 정수 벡터로 해석하라고 말해요. 문자열의 각 문자에 대해 하나씩이죠(ord 루틴이 정수 변환에 사용돼요). Raku는 각 정수에 차례로 형식을 적용한 뒤, 결과 문자열을 구분자(기본적으로 점 '.')로 결합해요. 임의 문자열에 있는 문자의 서수(ordinal) 값을 표시할 때 유용할 수 있어요.

NYI sprintf "%vd", "AB\x[100]";           # OUTPUT: «65.66.256␤»

매개변수 인덱스가 있는 별표(예: *2$v)를 사용해 구분자 문자열에 쓸 인자 번호를 명시적으로 지정할 수도 있어요. 예를 들어:

NYI sprintf '%*4$vX %*4$vX %*4$vX',       # 3 IPv6 addresses
        @addr[1..3], ":";

너비 (Width, 최소)

인자는 보통 주어진 값을 표시하는 데 필요한 만큼만 넓게 형식화돼요. 여기에 숫자를 넣어 기본 너비를 무시하는 최소 너비를 지정하거나, 다음 인자(*로) 또는 지정된 인자(예: *2$로)에서 원하는 너비를 얻을 수 있어요.

sprintf "<%s>", "a";           # OUTPUT: «<a>␤»
sprintf "<%6s>", "a";          # OUTPUT: «<     a>␤»
sprintf "<%*s>", 6, "a";       # OUTPUT: «<     a>␤»
NYI sprintf '<%*2$s>', "a", 6; # OUTPUT: «<     a>␤»
sprintf "<%2s>", "long";       # OUTPUT: «<long>␤»   (does not truncate)

모든 경우에 지정된 너비는 주어진 정수 수치 값이나 문자열을 수용하도록 필요에 따라 늘어나요. *로 얻은 필드 너비가 음수면 - 플래그와 같은 효과, 즉 왼쪽 정렬을 가져요.

정밀도, 또는 최대 너비

숫자 뒤에 '.'를 지정해 (숫자 변환의 경우) 정밀도 또는 (문자열 변환의 경우) 최대 너비를 지정할 수 있어요. 부동소수점 형식의 경우 gG를 제외하고, 소수점 오른쪽에 표시할 자리 수를 지정해요(기본값은 6). 예를 들어:

# These examples are subject to system-specific variation.
sprintf '<%f>', 1;    # OUTPUT: «"<1.000000>"␤»
sprintf '<%.1f>', 1;  # OUTPUT: «"<1.0>"␤»
sprintf '<%.0f>', 1;  # OUTPUT: «"<1>"␤»
sprintf '<%e>', 10;   # OUTPUT: «"<1.000000e+01>"␤»
sprintf '<%.1e>', 10; # OUTPUT: «"<1.0e+01>"␤»

gG의 경우 소수점 앞과 뒤의 자릿수를 포함해 표시할 최대 자릿수를 지정해요. 예를 들어:

# These examples are subject to system-specific variation.
sprintf '<%g>', 1;        # OUTPUT: «<1>␤»
sprintf '<%.10g>', 1;     # OUTPUT: «<1>␤»
sprintf '<%g>', 100;      # OUTPUT: «<100>␤»
sprintf '<%.1g>', 100;    # OUTPUT: «<1e+02>␤»
sprintf '<%.2g>', 100.01; # OUTPUT: «<1e+02>␤»
sprintf '<%.5g>', 100.01; # OUTPUT: «<100.01>␤»
sprintf '<%.4g>', 100.01; # OUTPUT: «<100>␤»

정수 변환의 경우 정밀도를 지정하면 숫자 자체의 출력이 이 너비로 0 패딩되어야 함을 뜻해요(0 플래그는 무시돼요). (이 기능은 현재 부호 없는 정수 변환에서 동작하지만 부호 있는 정수에서는 동작하지 않는다는 점을 기억하세요.)

sprintf '<%.6d>', 1;         # OUTPUT: «<000001>␤»
NYI sprintf '<%+.6d>', 1;    # OUTPUT: «<+000001>␤»
NYI sprintf '<%-10.6d>', 1;  # OUTPUT: «<000001    >␤»
sprintf '<%10.6d>', 1;       # OUTPUT: «<    000001>␤»
NYI sprintf '<%010.6d>', 1;  # OUTPUT: «<    000001>␤»
NYI sprintf '<%+10.6d>', 1;  # OUTPUT: «<   +000001>␤»
sprintf '<%.6x>', 1;         # OUTPUT: «<000001>␤»
sprintf '<%#.6x>', 1;        # OUTPUT: «<0x000001>␤»
sprintf '<%-10.6x>', 1;      # OUTPUT: «<000001    >␤»
sprintf '<%10.6x>', 1;       # OUTPUT: «<    000001>␤»
sprintf '<%010.6x>', 1;      # OUTPUT: «<    000001>␤»
sprintf '<%#10.6x>', 1;      # OUTPUT: «<  0x000001>␤»

문자열 변환의 경우 정밀도를 지정하면 문자열을 지정된 너비에 맞게 잘라요.

sprintf '<%.5s>', "truncated";   # OUTPUT: «<trunc>␤»
sprintf '<%10.5s>', "truncated"; # OUTPUT: «<     trunc>␤»

.*로 다음 인자에서, 또는 지정된 인자(예: .*2$)에서 정밀도를 얻을 수도 있어요.

sprintf '<%.6x>', 1;           # OUTPUT: «<000001>␤»
sprintf '<%.*x>', 6, 1;        # OUTPUT: «<000001>␤»
NYI sprintf '<%.*2$x>', 1, 6;  # OUTPUT: «<000001>␤»
NYI sprintf '<%6.*2$x>', 1, 4; # OUTPUT: «<  0001>␤»

*로 얻은 정밀도가 음수면 정밀도가 전혀 없는 것으로 간주돼요.

sprintf '<%.*s>',  7, "string";   # OUTPUT: «<string>␤»
sprintf '<%.*s>',  3, "string";   # OUTPUT: «<str>␤»
sprintf '<%.*s>',  0, "string";   # OUTPUT: «<>␤»
sprintf '<%.*s>', -1, "string";   # OUTPUT: «<string>␤»
sprintf '<%.*d>',  1, 0;          # OUTPUT: «<0>␤»
sprintf '<%.*d>',  0, 0;          # OUTPUT: «<>␤»
sprintf '<%.*d>', -1, 0;          # OUTPUT: «<0>␤»

크기 (Size)

숫자 변환에서는 l, h, V, q, L, 또는 ll을 사용해 숫자를 해석할 크기를 지정할 수 있어요. 정수 변환(d u o x X b i D U O)에서 숫자는 보통 플랫폼의 기본 정수 크기(보통 32 또는 64비트)로 간주되지만, Raku를 빌드하는 데 사용된 컴파일러가 지원하는 표준 C 타입 중 하나를 대신 사용하도록 재정의할 수 있어요.

(참고: 다음 중 어떤 것도 아직 구현되지 않았어요.)

hh 정수를 C 타입 "char" 또는 "unsigned char"로 해석
h 정수를 C 타입 "short" 또는 "unsigned short"로 해석
j 정수를 C 타입 "intmax_t"로 해석, C99 컴파일러에서만 (이식 불가)
l 정수를 C 타입 "long" 또는 "unsigned long"으로 해석
q, L, ll 정수를 C 타입 "long long", "unsigned long long", 또는 "quad"(보통 64비트 정수)로 해석
t 정수를 C 타입 "ptrdiff_t"로 해석
z 정수를 C 타입 "size_t"로 해석

인자 순서 (Order of arguments)

보통 sprintf는 각 형식 지정에 대해 다음 미사용 인자를 형식화할 값으로 가져와요. 형식 지정이 *를 사용해 추가 인자를 요구하면, 그 인자는 형식 지정에 등장하는 순서대로 형식화할 값보다 먼저 인자 목록에서 소비돼요. 명시적 인덱스로 인자가 지정된 경우, 명시적으로 지정된 인덱스가 다음 인자였더라도 인자의 정상적인 순서에는 영향을 주지 않아요.

그래서:

my $a = 5; my $b = 2; my $c = 'net';
sprintf "<%*.*s>", $a, $b, $c; # OUTPUT: «<   ne>␤»

$a를 너비로, $b를 정밀도로, $c를 형식화할 값으로 사용해요.

NYI sprintf '<%*1$.*s>', $a, $b;

$a를 너비와 정밀도로, $b를 형식화할 값으로 사용할 거예요.

몇 가지 더 많은 예시를 보겠어요. 명시적 인덱스를 사용할 때 형식 문자열이 큰따옴표로 묶여 있으면 $를 이스케이프해야 한다는 점을 유의하세요.

sprintf "%2\$d %d\n",      12, 34;         # OUTPUT: «34 12␤␤»
sprintf "%2\$d %d %d\n",   12, 34;         # OUTPUT: «34 12 34␤␤»
sprintf "%3\$d %d %d\n",   12, 34, 56;     # OUTPUT: «56 12 34␤␤»
NYI sprintf "%2\$*3\$d %d\n",  12, 34,  3; # OUTPUT: « 34 12␤␤»
NYI sprintf "%*1\$.*f\n",       4,  5, 10; # OUTPUT: «5.0000␤␤»

다른 예시:

NYI sprintf "%ld a big number", 4294967295;
NYI sprintf "%%lld a bigger number", 4294967296;
sprintf('%c', 97);                  # OUTPUT: «a␤»
sprintf("%.2f", 1.969);             # OUTPUT: «1.97␤»
sprintf("%+.3f", 3.141592);         # OUTPUT: «+3.142␤»
sprintf('%2$d %1$d', 12, 34);       # OUTPUT: «34 12␤»
sprintf("%x", 255);                 # OUTPUT: «ff␤»

특수한 경우: sprintf("<b>%s</b>\n", "Raku")는 동작하지 않지만, 다음 중 하나는 동작해요.

sprintf Q:b "<b>%s</b>\n",  "Raku"; # OUTPUT: «<b>Raku</b>␤␤»
sprintf     "<b>\%s</b>\n", "Raku"; # OUTPUT: «<b>Raku</b>␤␤»
sprintf     "<b>%s\</b>\n", "Raku"; # OUTPUT: «<b>Raku</b>␤␤»

sub flat

multi flat(**@list)
multi flat(Iterable \a)

제공된 어떤 인자든 담은 리스트를 만들고, 그 리스트 또는 Iterable.flat 메서드(Any에서 상속)를 호출한 결과를 반환해요.

say flat 1, (2, (3, 4), $(5, 6)); # OUTPUT: «(1 2 3 4 (5 6))␤»

routine unique

multi unique(+values, |c)

호출 객체/인자 목록에서 고유한 값들의 시퀀스를 반환해요. 각 중복 값의 첫 번째 발생만 결과 목록에 남아요. unique는 선택적 :with 매개변수가 다른 비교자로 지정되지 않는 한 두 객체가 같은지 판단하는 === 연산자의 의미를 사용해요. 중복을 제거해도 원래 목록의 순서는 유지돼요.

예시:

say <a a b b b c c>.unique;   # OUTPUT: «(a b c)␤»
say <a b b c c b a>.unique;   # OUTPUT: «(a b c)␤»

(입력이 동일한 객체가 인접하도록 정렬된 것을 안다면 대신 squish를 사용해요.)

선택적 :as 매개변수는 unique를 적용하기 전에 요소를 정규화/표준화할 수 있게 해줘요. 값은 비교 목적으로 변환되지만, 결과 목록에 들어가는 것은 여전히 원래 값이에요. 다만 첫 번째 발생만 그 목록에 나타나요.

예시:

say <a A B b c b C>.unique(:as(&lc))      # OUTPUT: «(a B c)␤»

선택적 :with 매개변수로 비교자를 지정할 수도 있어요. 예를 들어 고유한 해시 목록을 원한다면 eqv 비교자를 쓸 수 있어요.

예시:

my @list = %(a => 42), %(b => 13), %(a => 42);
say @list.unique(:with(&[eqv]))           # OUTPUT: «({a => 42} {b => 13})␤»

참고: :with Callable은 목록의 모든 항목과 시도해봐야 하므로, 이는 unique가 훨씬 높은 알고리즘 복잡도를 가진 경로를 따르게 해요. 가능하면 :as 인자를 쓰도록 노력하세요.

routine repeated

multi repeated(+values, |c)

호출 객체/인자 목록에서 반복된 값들의 시퀀스를 반환해요. unique와 같은 매개변수를 받지만, 요소를 처음 볼 때 통과시키는 대신 두 번째(또는 그 이상)로 볼 때만 통과시켜요.

예시:

say <a a b b b c c>.repeated;                   # OUTPUT: «(a b b c)␤»
say <a b b c c b a>.repeated;                   # OUTPUT: «(b c b a)␤»
say <a A B b c b C>.repeated(:as(&lc));         # OUTPUT: «(A b b C)␤»

my @list = %(a => 42), %(b => 13), %(a => 42);
say @list.repeated(:with(&[eqv]))               # OUTPUT: «({a => 42})␤»

unique의 경우와 마찬가지로 결합(associative) 인자 :as는 비교 전에 요소를 정규화하는 Callable을, :with는 사용할 동등 비교 함수를 받아요.

routine squish

sub squish( +values, |c)

호출 객체/인자 목록에서 하나 이상의 값의 연속(run)이 첫 번째 인스턴스만으로 대체된 값들의 시퀀스를 반환해요. unique처럼 squish는 두 객체가 같은지 판단하는 데 === 연산자의 의미를 사용해요. unique와 달리 이 함수는 인접한 중복만 제거해요. 더 멀리 떨어진 동일한 값은 계속 유지되죠. 중복을 제거해도 원래 목록의 순서는 유지돼요.

예시:

say <a a b b b c c>.squish; # OUTPUT: «(a b c)␤»
say <a b b c c b a>.squish; # OUTPUT: «(a b c b a)␤»

선택적 :as 매개변수는 unique와 마찬가지로 비교 전에 값을 임시로 변환할 수 있게 해줘요.

선택적 :with 매개변수는 적절한 비교 연산자를 설정하는 데 사용돼요.

say [42, "42"].squish;                      # OUTPUT: «(42 42)␤»
# Note that the second item in the result is still Str
say [42, "42"].squish(with => &infix:<eq>); # OUTPUT: «(42)␤»
# The resulting item is Int

sub sleep

sub sleep($seconds = Inf --> Nil)

주어진 $seconds만큼 잠자려 시도해요. 완료되면 Nil을 반환해요. 인자로 Int, Num, Rat, 또는 Duration 타입을 받아요. 이들은 모두 Real도 하기 때문이에요.

sleep 5;                # Int
sleep 5.2;              # Num
sleep (5/2);            # Rat
sleep (now - now + 5);  # Duration

따라서 정수가 아닌 시간만큼 잠자는 것도 가능해요. 예를 들어 다음 코드는 sleep (5/2)가 2.5초, sleep 5.2가 5.2초 동안 잠잔다는 것을 보여줘요.

my $before = now;
sleep (5/2);
my $after = now;
say $after-$before;  # OUTPUT: «2.502411561␤»

$before = now;
sleep 5.2;
$after = now;
say $after-$before;  # OUTPUT: «5.20156987␤»

sub sleep-timer

sub sleep-timer(Real() $seconds = Inf --> Duration:D)

이 함수는 sleep처럼 구현되지만, 전자와 달리 시스템이 잠들지 않은 초 수를 담은 Duration 인스턴스를 반환해요.

특히 반환된 Duration은 프로세스가 어떤 외부 이벤트(예: 가상 머신 또는 운영체제 이벤트)에 의해 깨어났을 때 남은 초 수를 처리해요. 정상적인 조건에서 sleep이 중단되지 않으면 반환된 Duration의 값은 0이에요. 즉 남은 잠잘 시간이 없다는 뜻이죠. 따라서 일반적인 상황에서는:

say sleep-timer 3.14;  # OUTPUT: «0␤»

음수 또는 0의 잠잘 시간이 인자로 전달되는 엣지 케이스에도 같은 결과가 적용돼요.

say sleep-timer -2; # OUTPUT: 0
say sleep-timer 0;  # OUTPUT: 0

sleep-until도 함께 보세요.

sub sleep-until

sub sleep-until(Instant $until --> Bool)

sleep와 비슷하게 동작하지만 현재 시간을 검사하고, 요구된 미래의 순간에 도달할 때까지 계속 잠을 자요. 내부적으로 sleep-timer 메서드를 루프로 사용해, 실수로 일찍 깨어나면 지정된 순간에 도달할 때까지 남은 시간만큼 다시 기다리도록 보장해요.

미래의 Instant에 도달했으면(잠을 통해 또는 지금 바로이기 때문에) True를, 과거의 Instant가 지정된 경우에는 False를 반환해요.

10초 뒤까지 잠을 자려면 대략 이렇게 쓸 수 있어요.

say sleep-until now+10;   # OUTPUT: «True␤»

과거의 시간까지 잠을 자려는 것은 동작하지 않아요.

my $instant = now - 5;
say sleep-until $instant; # OUTPUT: «False␤»

하지만 순간을 충분히 먼 미래로 두면 sleep이 실행돼야 해요.

my $instant = now + 30;
# assuming the two commands are run within 30 seconds of one another...
say sleep-until $instant; # OUTPUT: «True␤»

미래의 정확한 순간을 지정하려면 먼저 적절한 시점에 DateTime을 만들고 Instant로 캐스팅해요.

my $instant = DateTime.new(
    year => 2023,
    month => 9,
    day => 1,
    hour => 22,
    minute => 5);
say sleep-until $instant.Instant; # OUTPUT: «True␤» (eventually...)

이것은 기본적인 알람 시계의 일종으로 쓸 수 있어요. 예를 들어 2015년 9월 4일 아침 7시에 일어나야 하는데, 평소 알람 시계가 고장나고 노트북만 있다고 해보죠. 일어날 시간을(시간대에 주의하세요. DateTime.new는 기본적으로 UTC를 사용하니까요) Instant로 지정해 sleep-until에 넘기면, 이후 평소 알람 시계 대신 mp3 파일을 재생해 깨울 수 있어요. 이 시나리오는 대략 이렇게 생겼어요.

# DateTime.new uses UTC by default, so get timezone from current time
my $timezone = DateTime.now.timezone;
my $instant = DateTime.new(
    year => 2015,
    month => 9,
    day => 4,
    hour => 7,
    minute => 0,
    timezone => $timezone
).Instant;
sleep-until $instant;
qqx{mplayer wake-me-up.mp3};

sub emit

sub emit(\value --> Nil)

어떤 supply나 react 블록 밖에서 사용하면 emit without supply or react 예외를 던져요. Supply 블록 안에서는 스트림에 메시지를 추가해요.

my $supply = supply {
  for 1 .. 10 {
      emit($_);
  }
}
$supply.tap( -> $v { say "First : $v" });

emit은 제어 예외(control exception)로 구현되어 있으므로, 그것이 참조하는 supply 블록에서 호출되는 어떤 것에 의해서도 사용될 수 있어요.

emit 메서드에 대한 페이지도 함께 보세요.

sub undefine

multi undefine(Mu    \x)
multi undefine(Array \x)
multi undefine(Hash  \x)

6.d 언어 버전에서 deprecated되며 6.e에서 제거될 예정이에요. ArrayHash에 대해서는 Empty를 할당하는 것과 동등해지고, 다른 모든 것에는 Nil을 할당하는 것과 동등해져요. (Empty 또는 Nil을 쓰는 것이 권장돼요.)

배열 조작 (Array manipulation)

배열과 다른 가변 컬렉션을 조작하는 루틴들이에요.

sub pop

multi pop(@a) is raw

Positional 인자에 pop 메서드를 호출해요. 그 메서드는 마지막 요소를 제거하고 반환하거나, 컬렉션이 비어 있으면 X::Cannot::Empty를 감싼 Failure를 반환해야 해요.

예시는 Array 메서드 문서를 참고하세요.

sub shift

multi shift(@a) is raw

Positional 인자에 shift 메서드를 호출해요. 실제로 구현하는 가변 컬렉션(예: Array 또는 Buf)에서 그 메서드는 첫 번째 요소를 제거하고 반환하거나, 컬렉션이 비어 있으면 Failure를 반환해야 해요.

예시:

say shift [1,2]; # OUTPUT: «1␤»
my @a of Int = [1];
say shift @a; # OUTPUT: «1␤»
say shift @a; # ERROR: «Cannot shift from an empty Array[Int]␤»

sub push

multi push(\a, **@b is raw)
multi push(\a, \b)

첫 번째 인자에 push 메서드를 호출하며 나머지 인자를 전달해요. push 메서드는 제공된 값을 컬렉션의 끝(또는 그 부분)에 추가해야 해요. 이 서브루틴을 통한 간접(indirection)이 도움이 될 수 있는 예시는 Hash 메서드 문서를 참고하세요.

push 메서드는 모든 Slip 타입 인자를 평평하게(flatten) 만들어야 해요. 따라서 새 컬렉션 타입에 대한 준수하는 메서드를 구현하려면 시그니처가 그냥 다음과 같아야 해요.

multi method push(::?CLASS:D: **@values is raw --> ::?CLASS:D)

새 타입에 대한 autovivification은, 새 타입이 Positional 롤을 구현하면 기본 베이스 클래스가 제공해요. 새 타입이 Positional이 아니면 autovivification은 다음과 같은 시그니처의 multi 메서드를 추가해 지원할 수 있어요.

multi method push(::?CLASS:U: **@values is raw --> ::?CLASS:D)

sub append

multi append(\a, **@b is raw)
multi append(\a, \b)

첫 번째 인자에 append 메서드를 호출하며 나머지 인자를 전달해요. append 메서드는 제공된 값을 컬렉션의 끝(또는 그 부분)에 추가해야 해요. push 메서드와 달리 append 메서드는 단일 인자 규칙을 따라야 해요. 따라서 새 컬렉션 타입에 대한 준수하는 append 메서드를 구현하려면 시그니처가 그냥 다음과 같아야 해요.

multi method append(::?CLASS:D: +values --> ::?CLASS:D)

push 루틴과 비슷하게, autovivification을 지원하려면 multi 메서드를 추가해야 할 수 있어요.

multi method append(::?CLASS:U: +values --> ::?CLASS:D)

append의 서브루틴 형태는 Hash의 값에 덧붙일 때 도움이 될 수 있어요. append 메서드는 명명 인자로 해석되는 리터럴 페어를 조용히 무시하는 반면, 서브루틴은 던지기 때문이에요.

my %h = i => 0;
append %h, i => (1, 42);
CATCH { default { put .message } };
# OUTPUT: «Unexpected named argument 'i' passed␤»

제어 루틴 (Control routines)

프로그램의 흐름을 바꾸고, 어쩌면 값을 반환하는 루틴들이에요.

sub exit

multi exit()
multi exit(Int(Any) $status)

반환 코드 $status(값이 지정되지 않았으면 0)로 현재 프로세스를 종료해요. 0과 다른 exit 값($status)은 그것을 잡는 프로세스(예: 쉘)가 적절히 평가해야 해요. 이것이 Main에서 0과 다른 exit 코드를 반환하는 유일한 방법이에요.

exitLEAVE phaser가 실행되는 것을 막지만, &*EXIT 변수의 코드는 실행해요.

exit는 부모 프로세스에 0과 다른 exit 코드를 알리기 위한 최후의 수단으로만 사용해야 해요. 메서드나 서브를 예외적으로 종료하는 데 쓰지 마세요. 대신 예외를 사용해요.

프로세스에서 exit의 첫 번째 호출이 같은 스레드든 다른 스레드든 이후의 어떤 exit 호출과 무관하게 반환 코드를 설정해요.

sub done

sub done(--> Nil)

어떤 supply나 react 블록 밖에서 사용하면 done without supply or react 예외를 던져요. Supply 블록 안에서는 supply가 더 이상 값을 방출하지 않음을 알리고 supply 블록을 떠나요. done 메서드에 대한 문서도 함께 보세요.

done은 제어 예외로 구현되어 있으므로, 그것이 참조하는 supplyreact 블록에서 호출되는 어떤 것에 의해서도 사용될 수 있어요.

my $supply = supply {
    for 1 .. 3 {
        emit($_);
    }
    done;
    say "never reached";
}
$supply.tap( -> $v { say "Second : $v" }, done => { say "No more" });
# OUTPUT: «Second : 1␤Second : 2␤Second : 3␤No More␤»

tap할 때 supply에 전달된 done 명명 인자의 블록은 supply 블록 안에서 done이 호출될 때 실행돼요. 마찬가지로 supply를 tap하는 데 사용된 whenever 블록에서는 어떤 LAST phaser가 호출돼요.

Rakudo 컴파일러 2021.06 릴리스부터는 done에 값을 제공하는 것도 가능해요.

sub done($value --> Nil)

지정된 값이 먼저 emit된 다음 인자 없는 done이 호출돼요.

my $supply = supply {
    for 1 .. 3 {
        emit($_);
    }
    done 42;  # same as: emit 42; done
}
$supply.tap: -> $v { say "Val: $v" }, done => { say "No more" }
# OUTPUT: OUTPUT: «Val: 1␤Val: 2␤Val: 3␤Val: 42␤No More␤»

sub lastcall

sub lastcall(--> True)

현재 디스패치 체인을 잘라내요. 즉 nextsame, callsame, nextwith, callwith 호출이 이후의 후보를 찾지 못하게 해요. samewith는 디스패치를 처음부터 다시 시작하므로 체인을 lastcall로 자르는 것의 영향을 받지 않는다는 점을 기억하세요.

아래 예시를 보죠. foo(6)lastcall이 호출되지 않았을 때 nextsame을 사용해 Any 후보에 도달해요. foo(2)nextsame을 호출하지만, lastcall이 먼저 호출되어 디스패치 체인이 잘렸기 때문에 Any 후보에 도달하지 못해요. 마지막 호출 foo(1)lastcall을 호출하지만, 그 후 samewith를 사용하는데, 이는 그 영향을 받지 않아 디스패치가 처음부터 다시 시작되어 새 인자 6으로 Int 후보에 도달하고, 그다음 (그 samewith가 호출되기 전에 사용된 lastcall의 영향을 받지 않는) nextsame을 통해 Any 후보로 진행해요.

multi foo (Int $_) {
    say "Int: $_";
    lastcall   when *.is-prime;
    nextsame   when *  %% 2;
    samewith 6 when * !%% 2;
}
multi foo (Any $x) { say "Any $x" }

foo 6; say '----';
foo 2; say '----';
foo 1;

# OUTPUT:
# Int: 6
# Any 6
# ----
# Int: 2
# ----
# Int: 1
# Int: 6
# Any 6