Raku 자주 묻는 질문

Raku 자주 묻는 질문 (FAQ)

Raku를 처음 시작하면서 가장 많이 하는 질문들을 모아 둔 문서예요. 언어와 구현(Rakudo), 모듈 설치, 흔한 문자열 조작, 그리고 "왜 이렇게 생겼지?" 싶은 문법까지, 궁금할 만한 것들을 질문 형식으로 정리해 두었어요.

출처: Raku 공식 문서 — FAQ

일반 (General)

Raku, Rakudo, Perl 6의 차이는 뭔가요?

엄밀히 말하면 Rakudo는 Raku의 구현(implementation)이에요. 지금은 Rakudo가 유일하게 개발 중인 구현이지만, 과거에는 다른 구현도 있었고 미래에도 다른 구현이 나올 가능성이 있어요. Raku는 언어의 정의(definition)예요. 현재 인터프리터를 말할 때는 Rakudo와 Raku를 서로 바꿔 쓸 수 있어요. "Perl 6"은 2019년 10월 이전에 "Raku"를 가리키던 이름이에요.

Raku는 언제 출시됐나요?

Rakudo 2015.12 구현 버전이 2015년 12월 25일에 출시됐는데, 이 버전은 같은 시점에 출시된 v6.c Raku 사양을 구현했어요.

Raku 버전 6.0.0이 있나요?

없어요. 첫 안정 언어 사양 버전은 v6.c("Christmas")예요. 미래의 사양 버전은 포인트 릴리스(예: v6.d.2) 또는 메이저 릴리스(예: v6.e)를 가질 수 있어요.

raku -v를 실행하면 컴파일러가 구현한 언어 버전이 표시돼요. 예를 들면:

$ raku -v
Welcome to Rakudo™ v2022.04.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2022.04.

v6.d는 언제 출시됐나요?

v6.d 사양은 Diwali 2018, 즉 2018년 11월 6~7일에 출시됐어요. 편리한 시간대 기준으로요. 6.d는 Rakudo 컴파일러 2018.11 릴리스에서 기본으로 활성화됐어요.

6.d 기능의 대부분은 6.c 사양과 충돌하지 않아서 특별한 프래그마 없이도 이미 구현되어 Rakudo 컴파일러에서 사용 가능했어요. 더 작은 기능·동작 집합은 파일 맨 위에 use v6.d 프래그마가 있으면 자동으로 사용 가능해요. 언어 사양에 대한 나머지 대략 3100개의 새 커밋은 단지 이전에 정의되지 않았던 동작을 명확히 했어요.

Raku 사용자로서 무엇을 설치해야 하나요?

Mac 사용자는 https://rakudo.org/downloads/star 에서 최신 Rakudo Star DMG 바이너리 설치 프로그램을 쓸 수 있어요.

Windows 사용자는 Rakudo Star MSI 바이너리 설치 프로그램을 쓸 수 있어요. zef로 라이브러리 모듈을 설치하려면 Windows Git과 Strawberry Perl이 필요해요.

Linux 사용자는 아마 Rakudo Star를 다운로드해서 https://www.rakudo.org/downloads/ 의 컴파일 지침을 따라야 할 거예요.

공급업체와 제3자가 제공하는 Linux·Mac 바이너리도 있을 텐데, 제조사 버전은 구식일 수 있어요. Rakudo 2015.12 릴리스 이전 버전은 피해야 해요.

공식 Rakudo Star docker 이미지는 https://hub.docker.com/_/rakudo-star/ 에 있어요.

고급 사용자로서 Rakudo 개발을 추적하고 싶어요.

리포지토리를 클론해서 빌드하는 방법이 있어요. 이렇게 하면 최소한으로 테스트되고 심각한 버그가 있을 수 있는 진행 중인 작업이 설치돼요. Rakudo Raku 컴파일러에 기여하는 데 관심이 있다면 Z-Script 헬퍼 도구가 유용할 거예요.

마지막 공식 월간 릴리스를 설치하려면 https://raw.githubusercontent.com/rakudo/rakudo/master/VERSION 에 보이는 태그를 체크아웃하거나 헬퍼 명령을 설정하면 돼요.

어떤 사용자는 rakubrew를 쓰는데, 이걸로 여러 버전의 Rakudo를 병렬로 빠르게 설치할 수 있어요.

어느 쪽이든 zefrakudoc을 에코시스템에서 추가로 설치해야 할 거예요.

좋은 Raku 문서는 어디서 찾을 수 있나요?

공식 문서 웹사이트(특히 "Introduction" 섹션)를 참고하세요.

perl6book.com에는 종이책과 전자책 목록이 있어요.

제3자 기사를 읽을 때는 출판 날짜에 주의하세요. 2015년 12월 이전에 출판된 것은 아마 Raku의 출시 전 버전을 설명할 거예요.

우리의 헬프 채팅에서 실시간으로 도움을 받거나 채팅 로그를 검색해 이전 대화와 토론을 찾을 수 있어요.

Raku에 관한 책이 있나요?

알파벳순으로 몇 권 소개할게요.

  • Learning to program with Raku: First Steps, by JJ Merelo
  • Metagenomics, by Ken Youens-Clark
  • Parsing with Perl 6 Regexes and Grammars, by Moritz Lenz
  • Perl 6 at a Glance, by Andrew Shitov
  • Raku Fundamentals, by Moritz Lenz
  • Perl 6 Deep Dive, by Andrew Shitov
  • Think Perl 6: How to Think Like a Computer Scientist, by Laurent Rosenfeld.

출판됐거나 진행 중인 책 목록은 raku.org에서 관리되고 있어요.

Raku 사양이 뭔가요?

사양은 Raku의 공식 테스트 스위트를 가리켜요. roast라고 부르며 github에 호스팅돼 있어요. 테스트를 통과하는 컴파일러는 그 버전의 Raku 사양을 구현한 것으로 간주돼요.

roast의 master 브랜치는 아직 어떤 사양의 일부가 아닐 수 있는 최신 개발에 해당해요. 다른 브랜치는 특정 버전에 해당하는데, 예를 들어 "6.c-errata"가 있어요.

그래서 6.c-errata는 테스트의 오류("errata")를 고치는 것 외에는 바꾸지 않는 출시 언어 버전이고, master는 다음 언어 버전이 될 수 있는 미출시 진행 중 작업이에요. 그 현재 상태가 반드시 다음 언어 버전의 동작을 규정하지는 않아요. 새 추가 사항은 릴리스에 포함되는지 검토되기 때문이에요.

Raku 관련 용어 용어집이 있나요?

네, glossary를 참고하세요.

저는 Perl 프로그래머예요. Perl과 Raku의 차이점 목록은 어디 있나요?

문서의 Introduction 섹션에 여러 Perl to Raku 가이드가 있고, 그중 가장 중요한 것이 Overview예요.

빠른 시작 문서를 찾는 Ruby 프로그래머예요.

rb-nutshell 가이드를 참고하세요.

모듈 (Modules)

Raku용 제3자 라이브러리 모듈 저장소가 있나요?

네. 사용자로서 zef 모듈 설치 프로그램은 버전 번호가 가장 높은 모듈 버전을 자동으로 설치해요. 특정 버전 및/또는 특정 저자 버전을 원하면 zef로 그렇게 지정할 수도 있어요.

새 모듈 저자로서 fez 모듈 업로더로 모듈을 Raku 에코시스템에 업로드할 수 있어요. 배포판의 스켈레톤 설정을 도와주는 헬퍼 모듈도 여러 개 있는데, App::Mi6 같은 것이 있어요. 모듈이 배포될 준비가 되면 업로드도 도와줘요.

역사적으로는 PAUSE를 사용해 CPAN에 Raku 모듈을 업로드할 수도 있었고, 그 이전에는 Github/Gitlab을 사용해 모듈을 다운로드 가능하게 하는 방법이 있었어요. 이제 이것들은 새 모듈 저자가 시작하기에 가장 좋은 방법으로 간주되지 않아요.

Raku용 perldoc(명령줄 문서 뷰어) 같은 게 있나요?

네, rakudoc라고 부르며 에코시스템에 그 이름으로 있어요. Rakudo Star에는 번들로 포함되지만, Rakudo 월간 릴리스를 쓰는 경우 zef로 수동 설치해야 해요.

Raku에서 Perl 모듈을 사용할 수 있나요?

네, Inline::Perl5로 사용할 수 있어요. 대부분의 Perl 모듈과 잘 동작해요. Perl Catalyst와 DBI도 실행할 수 있어요.

Raku에서 C와 C++를 사용할 수 있나요?

Nativecall이 이걸 특히 쉽게 만들어 줘요.

Nativecall이 libfoo.so를 찾지 못하는데 libfoo.so.1.2만 있어요!

대부분의 Linux 시스템에서 공유 라이브러리는 특정 libfoo에 대해 libfoo.so.x.y.z 실제 파일이 있고, 그 다음에 libfoo.solibfoo.so.x라는 심링크 집합이 있도록 설치돼요. 예를 들어 ls /usr/local/lib/libxxhash.so*는 이렇게 반환해요.

/usr/local/lib/libxxhash.so -> libxxhash.so.0.6.5
/usr/local/lib/libxxhash.so.0 -> libxxhash.so.0.6.5
/usr/local/lib/libxxhash.so.0.6.5

일반적으로 Linux에서 libfoo-devlibfoo-devel(배포판에 따라 다름)을 설치하면 공유 라이브러리를 설치하고 심링크도 자동 설정해 줘요. 하지만 어떤 경우에는 질문에서처럼 libfoo.so.1.2만 있을 수 있어요.

그 경우에는 ABI/API 버전을 명시적으로 설정하는 is native 버전을 쓰면 돼요. 매뉴얼에 나와 있듯이요.

sub call-foo() is native('foo',v1.2);

전통적인 UNIX 라이브러리 함수들은 다 어디로 갔나요?

NativeCall로 접근하는 건 꽤 쉬워요.

에코시스템 모듈 POSIX도 사용 가능해요.

Rakudo에 핵심 표준 라이브러리가 있나요?

Rakudo Star 배포판에는 유용한 모듈이 많이 딸려 와요.

Rakudo 컴파일러 전용 릴리스에는 가장 기본적인 모듈 몇 개만 포함돼요.

더 많은 모듈은 에코시스템에서 찾을 수 있어요.

B::Deparse 같은 게 있나요? / AST를 어떻게 얻을 수 있나요?

프로그램의 AST를 보려면 --target=optimize 명령줄 옵션을 쓰세요. 예: raku --target=optimize -e 'say "hi"'

optimize 타겟은 정적 최적화기가 작업을 마친 후의 AST를 주고, ast 타겟은 그 단계 이전의 AST를 줘요. 사용 가능한 타겟 전체 목록을 보려면 raku --stagestats -e ""를 실행하세요.

프리컴파일(precompilation)이 뭔가요?

모듈을 처음 불러올 때 Rakudo는 그것을 바이트코드로 컴파일해요. 그런 다음 Rakudo는 컴파일된 바이트코드를 디스크에 저장하고 사용해요. 훨씬 빠르기 때문이에요.

모듈 간 순환 의존성을 가질 수 있나요?

아니요, 순환 의존성은 가질 수 없어요. 모듈 사이에 순환 의존성이 있으면 Circular module loading detected 오류가 나야 해요.

roles를 사용하면 원하는 것을 달성할 가능성이 아주 높아요. A.rakumodB.rakumod에 의존하고 B.rakumodA.rakumod에 의존하는 대신, A-Role.rakumodB-Role.rakumod를 만들고 A.rakumodB.rakumod의 클래스가 이 역할들을 각각 구현하게 할 수 있어요. 그러면 A-Role.rakumodB-Role.rakumod에 의존하면 되고 순환 의존성은 필요 없어요.

Raku에서 순환 의존성이 동작하지 않는 이유 중 하나는 원패스 파싱이에요. B를 파싱할 때 A가 무엇인지 알아야 하고, A를 파싱할 때 B가 무엇인지 알아야 하는데, 이는 명백히 무한 루프예요.

Raku에는 "1 파일 = 1 클래스" 제한이 없고, 단일 컴파일 유닛(예: 파일) 안의 순환 의존성은 스터빙(stubbing)으로 가능하다는 점에 주목하세요. 따라서 또 다른 해결책은 클래스들을 같은 컴파일 유닛으로 옮기는 거예요.

일반 연산 (Common operations)

문자열: 문자열에서 숫자를 파싱해 얻으려면 어떻게 하나요?

+ 접두어를 쓰세요.

say "42.123456789123456789";  # OUTPUT: «42.123456789123456789␤»
say +"42.4e-2";               # OUTPUT: «0.424␤»

이런 컨텍스트화(contextualization) 예제는 리터럴 숫자로 입력할 수 있는 어떤 문자열이든 수치화할 수 있어요. val 루틴은 그것을 올로모프(allomorph)로 변환해요. unival 루틴은 유니코드 코드포인트 하나를 변환해요.

문자열: 문자열이 부분 문자열을 포함하는지, 포함한다면 매치 인덱스를 얻으려면 어떻게 하나요?

.contains.indices를 쓰세요.

"az and az and az again".contains("az"); # OUTPUT: «True␤»
"az and az and az again".indices("az");  # OUTPUT: «(0 7 14)␤»

문자열: 문자열의 16진수 표현을 얻으려면 어떻게 하나요?

문자열의 각 바이트의 16진수 목록을 얻으려면(즉 hex 인코더), 먼저 .encode로 문자열을 Blob으로 변환하세요.

say "I ❤ 🦋".encode>>.base(16);  # OUTPUT: «(49 20 E2 9D A4 20 F0 9F A6 8B)␤»

.gist.raku 메서드가 변수 내성(introspection)에 유용할 수 있어요.

say "I ❤ 🦋".encode.raku;  # OUTPUT: «utf8.new(73,32,226,157,164,32,240,159,166,139)␤»
say "I ❤ 🦋".encode.gist;  # OUTPUT: «utf8:0x<49 20 E2 9D A4 20 F0 9F A6 8B>␤»

문자열: 인덱스로 문자열에서 일부 문자를 제거하려면 어떻게 하나요?

.comb으로 Seq로 변환하고, (-) 중위 연산자로 원치 않는 인덱스를 제거하세요.

say '0123456789'.comb[(^* (-) (1..3, 8).flat).keys.sort].join;  # OUTPUT: «045679␤»

문자열이 크면 .comb은 시간이 걸릴 수 있어요. 그 경우 .substr-rw가 더 빨라요.

multi postcircumfix:<[- ]> (Str:D $str is copy, +@indices) {
    for @indices.reverse {
        when Int   { $str.substr-rw($_,1) = '' }
        when Range { $str.substr-rw($_  ) = '' }
    }
    return $str;
}

say '0123456789'[- 1..3, 8 ];  # OUTPUT: «045679␤»

문자열: 문자열을 같은 부분으로 나누려면 어떻게 하나요?

.comb은 선택적인 Int를 받아요.

.say for 'abcdefghijklmnopqrstuvwxyz'.comb: 8;  # OUTPUT: «abcdefgh␤ijklmnop␤qrstuvwx␤yz»

언어 기능 (Language features)

Raku 데이터 구조를 덤프하려면 어떻게 하나요 (Perl의 Data::Dumper 등)?

전형적인 선택지는 dump되는 객체의 "gist"를 주는 gist 메서드를 사용하는 say 루틴을 쓰는 거예요. 더 상세한 출력은 perl 메서드(곧 $obj.raku로 대체될 예정이며 Rakudo 2019.11 릴리스부터 사용 가능)를 호출해서 얻을 수 있어요. 이 메서드는 보통 EVAL 가능한 코드로 객체 표현을 반환해요.

rakudo 구현을 쓰고 있다면 rakudo 전용 dd 루틴으로 덤프할 수 있는데, 그 출력은 raku와 비슷하지만 정보가 더 많아요.

예시:

my $foo = %( foo => 'bar' );
say $foo.raku;   # OUTPUT: «${:foo("bar")}␤»
say $foo;        # OUTPUT: «{foo => bar}␤»

# non-standard routine available in rakudo implementation:
dd $foo;         # OUTPUT: «Hash $foo = ${:foo("bar")}␤»

데이터 구조 덤프에 더 많은 제어를 주는 에코시스템 모듈도 여럿 있는데, 색상 출력 지원도 포함해요.

Rakudo 컴파일러는 왜 그렇게 사과하는 말투인가요?

출력에 SORRY!가 있으면 그 오류는 컴파일 시간 오류예요. 그 외에는 런타임 오류예요.

예시:

sub foo( Int $a, Int $b ) {...}
foo(1)     # ===SORRY!=== Error while compiling ...
say 1/0;   # Attempt to divide 1 by zero using div

(Any)는 뭔가요?

Any는 대부분의 객체가 상속받는 최상위 클래스예요. Any 타입 객체는 명시적 타입 제약이 없는 변수와 파라미터의 기본값이에요. 그래서 예를 들어 say 루틴으로 값을 가진 변수의 gist를 출력하면 (Any)가 찍히는 걸 보게 될 거예요.

my $foo;
say $foo; # OUTPUT: «(Any)␤»

my Int $baz;
say $baz; # OUTPUT: «(Int)␤»

my $bar = 70;
say $bar; # OUTPUT: «70␤»

변수에 정의된 값이 있는지 검사하려면 DEFINITE와 defined 루틴을 보세요. 정의됨을 검사하는 다른 구성체도 여럿 있어요. with, orwith, without 문장, //, andthen, notandthen, orelse 연산자, 그리고 타입 제약 스마일리(smileys) 같은 것들이요.

so는 뭔가요?

soBool로 강제하는 느슨한 우선순위(loose precedence) 연산자예요.

and&&의 낮은 우선순위 버전인 것처럼, ? 접두어 연산자와 같은 의미를 가져요.

예시:

say so 1|2 == 2;    # OUTPUT: «True␤»

이 예제에서 비교 결과(이것은 Junction이에요)는 출력되기 전에 Bool로 변환돼요.

시그니처에 있는 :D:U는 뭔가요?

Raku에서 클래스와 다른 타입은 객체이며 자기 자신 타입의 타입 체크를 통과해요.

예를 들어 변수를 선언하면

my Int $x = 42;

정수(즉 Int 클래스의 인스턴스)만 할당할 수 있는 게 아니라 Int 타입 객체 자체도 할당할 수 있어요.

$x = Int

타입 객체를 제외하고 싶다면 "definite"를 뜻하는 :D 타입 스마일리를 붙이면 돼요.

my Int:D $x = 42;
$x = Int;

# dies with:
# Type check failed in assignment to $x;
# expected Int:D but got Int

마찬가지로 :U는 정의되지 않은 값, 즉 타입 객체로 제약해요.

타입 객체와 인스턴스 중 무엇이든 명시적으로 허용하려면 :_를 쓰면 돼요.

시그니처의 -->는 뭔가요?

-->는 반환 제약으로, 타입 또는 정의된 값이에요.

타입 제약 예시:

sub divide-to-int( Int $a, Int $b --> Int ) {
        return ($a / $b).narrow;
}

divide-to-int(3, 2)
# Type check failed for return value; expected Int but got Rat

정의된 반환값 예시:

sub discard-random-number( --> 42 ) { rand }
say discard-random-number;
# OUTPUT: «42␤»

이 경우 반환값이 시그니처에 이미 지정되어 있으므로 마지막 값은 버려져요.

Junction에서 값을 추출하려면 어떻게 하나요?

Junction에서 값을 추출하고 싶다면 아마 뭔가 잘못하고 있는 거예요. Set을 쓰는 게 맞아요.

Junction은 그걸로 대수(algebra)를 하려는 게 아니라 매처(matcher)로 쓰려는 거예요.

어쨌든 하고 싶다면 그 목적으로 오토스레딩(autothreading)을 남용할 수 있어요.

sub junction-values(Mu $j) {
    my @values;
    -> Any $s { @values.push: $s }.($j);
    @values;
}

say junction-values(1|2|3).join(', ');
# prints 1, 2, 3 or a permutation thereof

Str이 불변이면 s///는 어떻게 동작하나요? Int가 불변이면 $i++는요?

Raku에서 많은 기본 타입의 값은 불변이지만, 그것을 담고 있는 변수는 불변이 아니에요. s/// 연산자는 변수에 대해 동작하며, 새로 만들어진 문자열 객체를 그 변수에 넣어요. 마찬가지로 $i++$i 변수에 대해 동작하므로, 그 변수의 값에만 동작하는 게 아니에요.

이걸 안다면 리터럴 문자열을 바꾸려 하지 않을 거예요(예: 'hello' ~~ s/h/H/;). 하지만 map을 사용해 실수로 비슷한 걸 할 수도 있어요.

my @foo = <hello world>.map: { s/h/H/ };

# dies with
# Cannot modify an immutable Str (hello)

my @bar = <hello world>».subst-mutate: 'h', 'H';

# dies with
# Cannot resolve caller subst-mutate(Str: Str, Str);
# the following candidates match the type but require
# mutable arguments: ...

원본 값을 제자리에서 수정하는 대신, 새 값을 반환하는 루틴이나 연산자를 쓰세요.

my @foo = <hello world>.map: { S/h/H/ };  # ['Hello','world']
my @bar = <hello world>».subst: 'h', 'H'; # ['Hello','world']

컨테이너에 대한 문서를 더 참고하세요.

배열 참조와 자동 역참조는 뭐가 문제인가요? @ 시길이 필요한가요?

Raku에서 거의 모든 것이 참조라서, 참조를 취한다는 말은 별 의미가 없어요. 스칼라 변수도 배열을 직접 담을 수 있어요.

my @a = 1, 2, 3;
say @a;                 # OUTPUT: «[1 2 3]␤»
say @a.^name;           # OUTPUT: «Array␤»

my $scalar = @a;
say $scalar;            # OUTPUT: «[1 2 3]␤»
say $scalar.^name;      # OUTPUT: «Array␤»

큰 차이는 스칼라 안의 배열이 목록 컨텍스트에서 값을 하나로 작동하는 반면, 배열은 기꺼이 반복된다는 거예요.

my @a = 1, 2, 3;
my $s = @a;

for @a { ... }          # loop body executed 3 times
for $s { ... }          # loop body executed only once

my @flat = flat @a, @a;
say @flat.elems;            # OUTPUT: «6␤»

my @nested = flat $s, $s;
say @nested.elems;          # OUTPUT: «2␤»

@( ... ) 또는 표현식에 .list 메서드를 호출해 목록 컨텍스트를 강제할 수 있고, $( ... ) 또는 .item 메서드로 아이템 컨텍스트를 강제할 수 있어요.

자세한 내용은 Perl 6: Sigils, Variables, and Containers 문서를 참고하세요.

왜 시길이 있나요? 없이도 안 되나요?

이유가 여럿 있어요.

  • 변수를 문자열에 쉽게 보간(interpolate)하게 해 줘요.
  • 다른 변수와 트윌길(twigil)을 위한 마이크로 네임스페이스를 형성해서 이름 충돌을 피하게 해 줘요.
  • 단수/복수 구분을 쉽게 해 줘요.
  • 필수 명사 표지(noun marker)를 쓰는 자연어처럼 작동해서, 우리 뇌가 처리하도록 만들어져 있어요.
  • 필수는 아니에요. sigil 없는 이름을 선언할 수 있으니까요(모호함을 감수한다면).

"Type Str does not support associative indexing."

아마 문자열 보간과 키 문자(HTML 태그 같은)를 섞으려 한 거예요.

my $foo = "abc";
say "$foo<html-tag>";

Raku는 $foo가 Hash이고 <html-tag>가 문자열 리터럴 해시 키라고 생각해요. 클로저를 사용해 이해시켜 주세요.

my $foo = "abc";
say "{$foo}<html-tag>";

Raku에 코루틴이 있나요? yield는요?

Raku에는 Python 같은 yield 문장이 없지만, 지연 목록(lazy list)을 통해 비슷한 기능을 제공해요. 지연 목록을 반환하는 루틴을 쓰는 인기 있는 방법이 두 가지 있어요.

# first method, gather/take
my @values = gather while have_data() {
    # do some computations
    take some_data();
    # do more computations
}

# second method, use .map or similar method
# on a lazy list
my @squares = (1..*).map(-> \x { x² });

private 속성을 new 메서드에서 초기화할 수 없는 이유와 해결 방법은?

다음 코드 샘플의 say 문장은

class A {
    has $!x;
    method show-x {
        return $!x;
    }
}
say A.new(x => 5).show-x;

5를 출력하지 않아요. private 속성은 private이므로 외부 세계에 보이지 않아요. 기본 생성자가 그것을 초기화할 수 있다면 공개 API로 새어 나갈 거예요. 그래서 이 특정 코드 샘플에서 $!x 속성은 기본 생성자에 의한 객체 생성 중에 초기화되지 않아요.

그래도 기본 생성자로 private 속성을 초기화하고 싶다면 submethod BUILD를 추가해 그 작업을 할 수 있어요.

class B {
    has $!x;
    submethod BUILD(:$!x) { }
    method show-x {
        return $!x;
    }
}
say B.new(x => 5).show-x;

BUILD는 기본 생성자에 의해(간접적으로, 자세한 내용은 Object Construction 참고) 사용자가 생성자에 전달한 모든 이름 인자와 함께 호출돼요. :$!x는 이름이 x인 이름 파라미터이고, 이름이 x인 이름 인자로 호출되면 그 값은 $!x 속성에 바인딩돼요.

하지만 그렇게 하면 안 돼요. 속성이 private으로 선언됐다면 클래스 외부 환경에 노출되면 안 돼요(객체 생성 중에도요). 반대로 속성이 public이라면 $.x처럼 선언해도 단점이 없어요. 외부 관점이 기본적으로 읽기 전용이고, 내부에서는 $!x로 계속 접근할 수 있으니까요.

say, put, print는 어떻게, 왜 다른가요?

가장 확실한 차이는 sayput은 출력 끝에 새 줄을 붙이고 print는 붙이지 않는 거예요.

하지만 또 다른 차이가 있어요. printput은 전달된 각 항목에 Str 메서드를 호출해 인자를 문자열로 변환하는 반면, saygist 메서드를 사용해요. gist 메서드(자신의 클래스에도 만들 수 있어요)는 인간 해석용 Str을 만드는 데 목적이 있어요. 그래서 객체 본질을 이해하는 데 중요하지 않다고 여겨지는 정보는 자유롭게 생략할 수 있어요.

다르게 말하면, $obj.Str은 문자열 표현을, $obj.gist는 인간이 빠르게 인식하기 좋은 그 객체의 짧은 요약을, $obj.raku는 객체를 재생성할 수 있는 Raku스러운 표현을 줘요.

예를 들어 타입 객체(일명 "undefined value")에서 Str 메서드가 호출되면 타입은 빈 문자열로 문자열화되고 warning이 던져져요. 반면 gist 메서드는 괄호 사이에 타입 이름을 반환해요 (값에는 타입 외에 아무것도 없다는 걸 나타내기 위해서요).

my Date $x;     # $x now contains the Date type object
print $x;       # empty string plus warning
say $x;         # OUTPUT: «(Date)␤»

객체의 디버깅 버전을 보여주고 싶다면 rakudo 전용 dd 루틴을 쓰는 게 더 나아요. 기본적으로 $obj.raku를 해서 STDOUT이 아니라 STDERR에 보여줘요. 그래서 프로그램의 "정상" 출력에 간섭하지 않아요.

요컨대 say는 가벼운 인간 해석에, dd는 가벼운 디버깅 출력에 최적화되어 있고, printput은 출력 생산에 더 일반적으로 적합해요.

put은 그래서 printsay의 하이브리드예요. print처럼 객체에 Str 메서드를 호출하고, say처럼 출력 끝에 새 줄을 추가해요.

tokenrule의 차이는 뭔가요?

regex, token, rule은 모두 정규식을 도입하지만 의미가 약간 달라요.

token:ratchet 또는 :r 수정자를 암시하는데, 이는 규칙이 역추적하지 않게 해요.

rule:ratchet:sigspace(줄여서 :s) 수정자를 모두 암시해요. 즉 rule은 역추적하지 않고, 정규식 텍스트의 공백을 <.ws> 호출로 취급해요(즉 공백을 매치하는데, 두 단어 문자 사이를 제외하면 선택 사항이에요). 정규식 시작과 분기(alternation) 각 가지 시작의 공백은 무시돼요.

regex는 암시된 수정자 없이 평범한 정규식을 선언해요.

diefail의 차이는 뭔가요?

die는 예외를 던져요.

failFailure 객체를 반환해요. (호출자가 호출 어휘 스코프에서 use fatal;을 선언했다면 fail은 반환하는 대신 예외를 던져요.)

Failure는 "던져지지 않은" 또는 "지연된" 예외예요. 예외를 담고 있는 객체이며, Failure를 일반 객체로 사용하려 하거나 싱크 컨텍스트에서 무시하려 하면 예외를 던져요.

Failuredefined 검사에서 False를 반환하고, exception 메서드로 예외를 추출할 수 있어요.

식별자에 콜론 쌍(colonpairs)을 쓸 수 있어요. 근거가 뭔가요?

식별자는 콜론 쌍을 포함할 수 있는데, 그것은 이름의 일부가 돼요. Larry Wall의 해당 이슈 답변에 따르면, 이미 콜론 쌍 메커니즘이 있었으므로, 고유하게 만들기 위해 인용이 필요한 비표준 문자(또는 그런 문자에 대한 고유한 문자열화를 가진 다른 정보)를 인용할 수 있는 어떤 이름이든 확장하는 데 그것을 쓰는 건 너무나 당연한 일이었다.

대부분의 사람들은 유니코드 문자를 어떻게 입력하나요?

운영체제, 윈도우 환경 및/또는 편집기에 따라 달라요. 유니코드 문자 입력에 대한 이 페이지는 가장 인기 있는 운영체제와 편집기에서 어떻게 하는지 지정해 줘요.

Raku 구현

어떤 Raku 구현이 있나요?

Rakudo는 활발히 개발 중인 유일한 컴파일러예요.

역사적인 구현에는 Niecza (.NET)와 Pugs (Haskell)가 있어요.

Rakudo는 어떤 언어로 작성됐나요?

짧은 답은 Rakudo가 거의 전적으로 Raku로 작성됐다는 거예요. 더 자세한 답은 Rakudo가 Raku와 NQP("Not Quite Perl")의 혼합으로 작성됐다는 거예요. NQP는 가상 머신을 위한 가벼운 Raku스러운 환경이에요. Raku 문법을 사용해 가상 머신(예: MoarVM, JVM)용 컴파일러와 라이브러리를 만드는 고수준 방법으로 설계됐어요.

NQP는 어떤 언어로 작성됐나요?

NQP는 (1) NQP 코드, (2) 기반 가상 머신이 쓰는 언어, (3) 일부 제3자 C 및 Java 라이브러리, (4) 빌드 과정의 이전 실행이 만든 일부 부트스트래핑 파일의 혼합이에요.

Raku는 Lisp인가요?

(not (not Nil))

스크립트를 독립 실행형 실행 파일로 컴파일할 수 있나요?

App::InstallerMaker::WiX 같은 도구를 사용하면 컴파일러와 스크립트를 패키지하는 설치 프로그램을 만들 수 있어요. 하지만 현재 사용 가능한 컴파일러는 아직 독립 실행형 실행 파일 생성을 지원하지 않아요.

도움을 주고 싶다면, MoarVM 백엔드의 Rakudo 컴파일러에서 https://github.com/MoarVM/MoarVM/issues/875 이슈가 이 문제를 논의하는 장소로 열려 있어요.

Raku 배포 (Raku distribution)

다음 Rakudo Star 버전은 언제 출시되나요?

Rakudo Star 릴리스는 보통 분기마다 나오며, 릴리스 공지는 rakudo.org에 게시돼요.

메타 질문과 옹호 (Metaquestions and advocacy)

Raku가 원래 Perl 6라고 불린 이유는 뭔가요?

… 다른 언어에서 더 높은 숫자가 나타낼 수 있는 모든 것들을 암시하지 않는 다른 이름과는 대조적으로요.

짧은 답은 Rule 1에 따른 Larry의 선택이었다는 거예요.

커뮤니티는 Perl과 Raku를 자매 언어로 간주해요. 공통점이 많고 같은 문제 공간의 많은 부분을 다루지만, Raku는 Perl을 대체하려는 게 아니에요. 사실 두 언어는 서로 상호운용돼요.

Raku는 언제 준비되나요? 지금 준비됐나요?

프로그래밍 언어와 그 컴파일러의 준비 상태는 이진 결정이 아니에요. 언어와 구현이 진화하면서 꾸준히 더 사용 가능하게 돼요. 당신의 필요에 따라 Raku와 그 컴파일러가 준비됐을 수도, 아닐 수도 있어요.

그렇긴 해도, 버전 6.c(Christmas 2015)는 언어로서 Raku의 첫 공식 릴리스이며, 검증 스위트와 그것을 통과하는 컴파일러가 함께 왔어요.

왜 Raku를 배워야 하나요? 뭐가 그렇게 대단한가요?

Raku는 다른 프로그래밍 언어에서는 보통 찾을 수 없는 많은 훌륭한 아이디어를 통합해요. 몇몇 다른 언어가 이 기능 중 일부를 제공하지만, 그 모두를 제공하는 언어는 없어요.

  • Raku는 절차적, 객체 지향, 함수형 프로그래밍 방법론을 모두 제공해요.
  • 데이터 구조에 불변 시길(sigil)을 사용하는 쉽게 사용할 수 있는 일관된 문법.
  • Annex #29를 포함한 전체 grapheme 기반 Unicode 지원.
  • 깔끔하고 더 읽기 쉬운 정규식, 훨씬 더 많은 기능과 함께 사용성의 다음 단계로. 이름 있는 정규식이 사용 편의를 높여요.
  • Junction으로 여러 가능성을 쉽게 검사. 예: $a == 1|3|42 ($a가 1 또는 3 또는 42와 같나?)
  • 전역 변수에 대한 어휘 스코프 대안을 제공하는 동적 변수.
  • "원격 작동(action at a distance)"을 막기 위한 컴포저빌리티와 어휘 스코프 강조. 예: 임포트는 항상 어휘 스코프.
  • 이해하기 쉬운 일관된 스코프 규칙과 클로저.
  • 클래스와 역할을 가진 강력한 객체 지향(모든 것이 객체로 보일 수 있어요). 상속. 서브타이핑. 코드 재사용.
  • 객체와 메타객체에 대한 내성(끝까지 거북이).
  • 코드를 생성하거나 파싱할 필요 없이 메타프로그래밍을 가능하게 하는 MetaObject Protocol.
  • 위치·이름 파라미터의 쉬운 언팩을 위한 서브루틴·메서드 시그니처.
  • arity, 타입, 선택적 추가 코드에 기반한, 같은 이름에 다른 시그니처를 가진 서브루틴/메서드를 위한 multi-dispatch.
  • 알 수 없는 서브루틴이나 불가능한 디스패치에 대한 컴파일 시간 오류 보고.
  • 추가 런타임 비용 없이 선택적 점진적 타입 체크. 선택적 타입 주석과 함께.
  • 컴파일러/런타임 상태의 내성에 기반한 고급 오류 보고. 더 유용하고 정확한 오류 메시지를 의미해요.
  • Phasers(예: BEGIN / END)로 스코프 진입/퇴출, 루프 first / last / next 및 그 외 특별한 컨텍스트에서 코드를 실행할 수 있어요.
  • 단순한 스레드와 락을 훨씬 넘어서는 암시적·명시적 멀티프로세싱을 위한 고수준 동시성 모델. Raku의 동시성은 풍부한 (컴포저블) 도구 집합을 제공해요.
  • 멀티코어 컴퓨터가 점점 더 많이 쓰이고, Raku에서는 병렬성(암시적, 예: >> 메서드, 명시적 start { code }) 덕분에 그것을 활용할 수 있어요. 무어의 법칙이 끝나가고 있으므로 이는 중요해요.
  • 비동기적 코드 실행을 위한 프로그래밍을 가능하게 하는 구조적 언어 지원.
  • Supply는 어떤 일이 일어날 때(타이머, 신호, 파일시스템 이벤트 같은) 코드를 실행할 수 있게 해 줘요.
  • react / whenever / supply 키워드로 대화형, 이벤트 주도 애플리케이션을 쉽게 만들 수 있어요.
  • 가능하면 지연 평가, 원하거나 필요할 때는 즉시 평가. 예를 들어 지연 목록, 심지어 피보나치 수열이나 모든 소수 같은 무한 지연 목록까지.
  • 더 빠르고, 하드웨어에 더 가까운 처리를 위한 네이티브 데이터 타입.
  • NativeCall로 C와 C++ 외부 라이브러리에 인터페이스하는 게 꽤 쉬워요.
  • Inline::Perl5와 Inline::Python으로 Perl(CPAN)과 Python 모듈에 인터페이스하는 게 꽤 쉬워요.
  • 모듈의 여러 버전을 동시에 설치하고 로드할 수 있어요.
  • 더 단순한 업데이트·업그레이드 정책 덕분에 시스템 관리가 단순해져요.
  • Rats(유리수) 때문에 정밀도 손실 없는 간단한 숫자 계산.
  • 데이터나 코드 파싱을 위한 확장 가능한 문법(Raku는 이것으로 자신을 파싱해요).
  • Raku는 매우 가변적인 언어예요(자신만의 함수, 연산자, 트레잇, 데이터 타입을 정의할 수 있고, 이는 파서를 수정해요).
  • 방대한 데이터 타입 선택, 더해 자신만의 타입을 만들 가능성.
  • 적절한 경계 검사를 가진 다차원 형태(shape) 또는 네이티브 배열.
  • 문법 파싱 중 언제든, 또는 특정 매치가 발생했을 때 코드를 실행.
  • 사용자 정의 연산자나 트레잇 추가는 서브루틴을 쓰는 것만큼 간단.
  • 어떤 연산자(시스템 또는 사용자 추가)든 하이퍼 연산자 자동 생성.
  • 다양한 백엔드에서 실행. 현재 MoarVM과 JVM, JavaScript는 개발 중, 더 추가될 수도.
  • 실행 중 핫 코드 경로의 런타임 최적화 (JIT).
  • 작은(예: Raspberry Pi) 및 큰 멀티프로세서 하드웨어에서 실행.
  • 가비지 컬렉션 기반: 제때 파괴되지 않으므로 참조 카운팅이 필요 없어요. 제때 동작에는 phasers를 쓰세요.
  • 메서드를 런타임에 어떤 인스턴스화된 객체에도 믹스인할 수 있어요. 예: 대역 외 데이터 추가를 허용하기 위해.
  • multi-dispatch와 자동 사용법 메시지 생성을 가진 MAIN 서브루틴으로 접근 가능한 쉬운 명령줄 인터페이스.
  • 더 적은 코드 줄로 더 컴팩트한 프로그램 생성. 이름의 Huffman 코딩으로 더 나은 가독성.
  • 단순한 반복자 인터페이스로 정의된 지연 목록. 어느 클래스든 단일 메서드만 최소 제공하면 제공할 수 있어요.
  • 식별자에 하이픈과 다른 비알파벳 ASCII 문자뿐 아니라 특정 유니코드 문자를 쓸 수 있어요. (테스트에서 밑줄 대신 하이픈을 쓰는 건 사용자들 사이에서 흔히 "kebab case"라고 불려요. "camel case"와 "snake case"도 참고: https://en.wikipedia.org/wiki/Letter_case#Special_case_styles.)
  • Raku의 모토는 Perl이 줄곧 가졌던 것과 같아요: "Perl은 다르다. 한마디로 Perl은 쉬운 일을 쉽게 만들고, 어려운 일을 불가능하게 만들지 않도록 설계됐다." 그리고 "There Is More Than One Way To Do It". 이제 여기에 -Ofun을 더해요.

Raku가 나에게 충분히 빠른가요?

무엇을 하느냐에 달려 있어요. Rakudo는 "먼저 제대로 동작하게 만든 다음 빠르게 만들자"는 철학으로 개발됐어요. 어떤 것에는 이미 빠르지만 다른 것에는 작업이 필요해요. Raku는 다른 동적 언어가 주지 않는 많은 단서를 JIT에 제공하므로, 성능 개선을 위한 여지가 많다고 생각해요.

다음의 대략적인 벤치마크는(그런 것에 대한 평소의 주의 사항과 함께) Raku 기능을 최대한 사용하면 비슷한 작업에서 Raku가 Perl보다 빠를 수 있음을 보여줘요. 동시에 골격만 사용하면 Perl이 더 빠를 수 있어요. Raku를 다른 언어와 비교할 때도 비슷한 상황을 볼 수 있어요.

자신의 시스템에서 시도해 보세요. 즐겁게 놀랄지도 몰라요!

예시:

# Raku version
class Foo { has $.i is rw };

for 1..1_000_000 -> $i {
    my $obj = Foo.new;
    $obj.i = $i;
}
# Perl version
package Foo;
use Moose;

has i => (is => 'rw');

__PACKAGE__->meta->make_immutable;

for my $i (1..1_000_000) {
    my $obj = Foo->new;
    $obj->i($i);
}

1;

# Another Perl version that offers bare-bones set of features
# compared to Moose/Raku's version but those are not needed in this
# specific, simple program anyway.
package Foo;
use Mojo::Base -base;

has 'i';

for my $i (1..1_000_000) {
    my $obj = Foo->new;
    $obj->i($i);
}

1;

성능 비교에도 쓸 수 있는 이 프로그램을 사용하고 싶을 거예요. Perl 호출에 perl -Mbigint를 쓰는 한 두 언어 모두에서 동작해요.

my ($prev, $current) = (1, 0);

for (0..100_000) {
    ($prev, $current) = ($current, $prev + $current);
}
print $current;