rb-nutshell — Ruby와 Raku 문법 차이 한눈에

rb-nutshell — Ruby와 Raku 문법 차이 한눈에 (Ruby to Raku nutshell)

Ruby 개발자가 Raku를 처음 만나면, "작동은 비슷한데 뭔가 다르네"라는 느낌을 받을 거예요. 이 페이지는 Ruby와 Raku 사이의 구문·의미에서의 높은 수준의 차이를 정리한 참조 문서예요. Ruby에서 동작하지만 Raku에서는 다르게 써야 하는 것들을 나열했어요. 초보자용 튜토리얼이나 Raku 개요가 아니라, Ruby 배경이 튼튼한 Raku 학습자를 위한 기술 참조라는 점을 분명히 해 둘게요.

출처: Ruby to Raku - nutshell - Raku Documentation

기본 문법

문장 끝 세미콜론

Ruby는 대부분의 문장 끝을 줄바꿈으로 감지해요 (몇 가지 예외가 있지만). 표현식이 완전하면 됐으니까요. 긴 표현식을 줄바꿈으로 나눌 때는 파싱이 계속되도록 줄 끝에 연산자를 남겨 두는 게 흔해요:

foo +     # In Ruby a trailing operator means parsing should continue
  bar +
  baz

Raku에서는 문장을 반드시 ;로 끝내야 해요. 그래야 더 나은 피드백과 긴 줄을 나누는 유연함을 얻을 수 있죠. 명시적 ;가 필요 없는 두 예외는, 블록의 마지막 문장과 블록의 닫는 중괄호 뒤(그 줄에 다른 게 없으면)예요:

my $x;
...;
if 5 < $x < 10 {
  say "Yep!";
  $x = 17         # No ; required before closing }
}                 # No ; required after closing } because of the newline
say "Done!";      # The ; is not required here if nothing follows

공백

Ruby는 strict mode와 경고가 켜져 있어도 공백 사용에 놀라울 만큼 유연해요:

# unidiomatic but valid Ruby
puts"Hello "+
(people [ i]
    . name
    ) . upcase+"!"if$greeted[i]<1

Raku도 프로그래머의 자유와 창의성을 지지하지만, 구문적 유연성과 "일관되고 결정적이며 확장 가능한 그래머"라는 설계 목표 사이에서 균형을 잡아요. 그 그래머는 단일 패스 파싱과 도움이 되는 오류 메시지를 지원하고, 커스텀 연산자를 깔끔하게 통합하며, 프로그래머가 실수로 의도를 잘못 쓰지 않게 해 줘요. 또 "코드 골프"는 약간 덜 강조되는데, Raku는 키 입력 수보다 개념에서의 간결함을 지향해요.

그 결과, Ruby에선 공백이 선택적인 구문 자리 여럿이 Raku에선 필수이거나 금지예요. 그런 제약 중 상당수는 실제 Ruby 코드에 별로 영향을 주지 않지만 (예: 배열 변수와 대괄호 사이에 공백 금지), 몇몇은 Ruby 해커들의 습관과 충돌할 수 있어요:

  • 인자 목록의 여는 괄호 앞에 공백이 있어서는 안 된다.
foo (3, 4, 1); # Not right in Ruby or Raku (in Raku this would
               # try to pass a single argument of type List to foo)
foo(3, 4, 1);  # Ruby and Raku
foo 3, 4, 1;   # Ruby and Raku - alternative parentheses-less style
  • 키워드 바로 뒤에는 공백이 필수다.
if(a < 0); ...; end         # OK in Ruby
my $a; ...;
if ($a < 0) { ... }         # Raku
if $a < 0 { ... }           # Raku, more idiomatic
while(x > 5); ...; end      # OK in Ruby
my $x; ...;
while ($x > 5) { ... }      # Raku
while $x > 5 { ... }        # Raku, more idiomatic
  • 후치(포스트픽스)/포스트서컴픽스 연산자 앞에는 공백이 (배열·해시 첨자 포함해서) 허용되지 않는다.
seen [ :fish ] = 1    # Ruby, not idiomatic but allowed
%seen< fish > = 1;    # Raku, no space allowed after 'seen'
  • 기존 후치/포스트서컴픽스 연산자와 충돌한다면 중위 연산자 앞에 공백이 필요하다.
n<1     # Ruby (in Raku this would conflict with postcircumfix < >)

메서드 호출

Ruby에서는 메서드 이름으로 문자열을 만들고 public_send로 호출할 수 있어요. Raku에서는 ."$methodname"처럼 항상 따옴표를 붙여서 해당하는 메커니즘을 써요:

my $object; my Str $methodname; my @args; ...;
$object."$methodname"(@args);   # Raku

따옴표를 빼면 Raku는 $methodname이 메서드의 단순 문자열 이름이 아니라 Method 객체를 담기를 기대해요.

변수, 시길, 스코프, 공통 타입

Ruby에서 변수는 주로 스코프를 나타내는 시길을 써요. $는 전역, @@는 클래스, @는 인스턴스, 시길 없음은 지역(파라미터 포함)이에요. & 시길은 메서드 참조를 나타내는 데도 쓰여요. 심볼은 :가 붙지만 변수가 아니므로 실제로 시길은 아니에요.

Raku에서 시길은 주로 담긴 값이 구현하는 역할을 나타내는 데 쓰여요. 즉 값의 타입(또는 최소한 인터페이스)을 나타내죠. 시길은 변수가 어떻게 쓰이든 불변이라, 변수 이름의 일부라고 생각하면 돼요.

변수의 스코프는 대신 선언 자체(my, has, our 등)로 나타내요.

변수 스코프

지역 변수에 대해 Ruby는 할당 시 암시적 변수 선언을 쓰고 현재 블록으로 제한돼요. Ruby에서 ifwhile 내장 구조의 내용은 블록도 스코프도 아니에요.

Raku는 명시적 스코프 표시자를 쓰고, 변수를 암시적으로 만들지 않아요. { ... }가 보이는 곳마다 스코프인데, 조건부나 루프의 본문도 포함해요. 흔히 쓰이는 스코프 선언:

foo = 7        # Ruby, variable scope is defined by first assignment and
               # extends to the end of the current block
my  $foo = 7;   # Raku, lexical scoped to the current block
our $foo = 7;   # Raku, package scoped
has $!foo = 7;  # Raku, instance scoped (attribute)

$ 스칼라

$ 시길은 항상 "스칼라" 변수(예: $name)에 쓰여요. 이들은 단일 값 컨테이너예요.

가장 범용적인 변수 타입으로, 내용에 제한이 없어요. $x[1], $x{"foo"}, $f("foo")처럼 내용을 다룰 수 있다는 점을 기억하세요.

@ 배열

@ 시길은 항상 "배열" 변수(예: @months, @months[2], 슬라이스용 @months[2, 4])에 쓰여요. @ 시길 변수는 Positional 역할(위치 인덱싱과 슬라이싱 기능)을 하는 것만 담을 수 있어요.

  • 인덱싱
puts months[2]; # Ruby
say @months[2]; # Raku
  • 값 슬라이싱
puts months[8..11].join(',') # Ruby
say @months[8..11].join(',') # Raku

% 해시

% 시길은 항상 "해시" 변수(예: %calories, %calories<apple>, %calories<pear plum>)에 쓰여요. % 시길 변수는 Associative 역할을 하는 것만 담을 수 있어요.

Ruby는 배열과 해시 모두 값에 접근할 때 대괄호를 써요. Raku는 해시에 중괄호를 대신 써요. 내용을 자동으로 따옴표 처리하는(따옴표 없는 문자열) 각괄호 버전도 있어요:

부사로 슬라이스 타입을 제어할 수 있어요.

  • 인덱싱
puts calories["apple"]  # Ruby
say %calories{"apple"}; # Raku
puts calories["apple"]  # Ruby
puts calories[:apple]   # Ruby, symbols for keys are common
say %calories<apple>;   # Raku - angle brackets instead of single-quotes
say %calories«"$key"»;  # Raku - double angles interpolate as double-quotes
  • 값 슬라이싱
puts calories.values_at('pear', 'plum').join(',') # Ruby
puts calories.values_at(%w(pear plum)).join(',')  # Ruby, pretty?
say %calories{'pear', 'plum'}.join(',');          # Raku
say %calories<pear plum>.join(',');               # Raku (prettier)
my $keys = 'pear plum';
say %calories« $keys ».join(','); # Raku, interpolated split
  • 키/값 슬라이싱
puts calories.slice('pear', 'plum')  # Ruby >= 2.5
say %calories{'pear', 'plum'}:kv.Hash;   # Raku - use :kv adverb
say %calories<pear plum>:kv.Hash;        # Raku (prettier version)

& 서브

& 시길은 Ruby의 &와 아주 비슷하게, 이름 있는 서브루틴/연산자의 함수 객체를 호출하지 않고 참조하는 데 쓰여요. 즉 이름을 "동사"가 아니라 "명사"로 쓴다는 뜻이에요. & 시길 변수는 Callable 역할을 하는 것만 담을 수 있어요.

add = -> n, m { n + m } # Ruby lambda for an addition function
add.(2, 3)              # => 5, Ruby invocation of a lambda
add.call(2, 3)          # => 5, Ruby invocation of a lambda
my &add = -> $n, $m { $n + $m }; # Raku addition function
&add(2, 3);                      # => 5, you can keep the sigil
add(2, 3);                       # => 5, and it works without it
def foo
  ...
end
foo_method = method(:foo);     # Ruby
sub foo { ... };
my &foo_method = &foo; # Raku
some_func(&say) # Ruby pass a function reference
sub some_func { ... };
some_func(&say) # Raku passes function references the same way

Ruby에서는 종종 마지막 파라미터로 블록을 넘기는데, 특히 DSL에서 그러해요. 이것은 yield로 불리는 암시적 파라미터이거나 &가 붙은 명시적 블록일 수 있어요. Raku에서는 Callable 파라미터를 항상 나열하고 변수 이름으로 호출해요 (yield 대신). 그리고 함수를 호출하는 다양한 방법이 있어요.

# Ruby, declare a method and call the implicit block argument
def f
  yield 2
end

# Ruby, invoke f, pass it a block with 1 argument
f do |n|
  puts "Hi #{n}"
end
# Raku, declare a method with an explicit block argument
sub f(&g:($)) {
  g(2)
}

# Raku, invoke f, pass it a block with 1 argument
# There are several other ways to do this
f(-> $n { say "Hi {$n}" }); # Explicit argument
f -> $n { say "Hi {$n}" };  # Explicit argument, no parenthesis

# Additionally, if 'f' is a method on instance 'obj' you can use ':'
# instead of parenthesis
my $obj; ...;
$obj.f(-> $n { say "Hi {$n}" });  # Explicit argument
$obj.f: -> $n { say "Hi {$n}" };  # Explicit argument, no parenthesis

* 슬러피 파라미터 / 인자 확장

Ruby에서 * 접두사로 나머지 전달 파라미터를 배열로 슬러프하는 인자를 선언할 수 있어요. Raku도 비슷하게 동작해요:

def foo(*args); puts "I got #{args.length} args!"; end # Ruby
sub foo(*@args) { say "I got #{@args.elems} args!" }   # Raku

위 Raku 버전은 슬러프해서 @args에 담을 때 중첩 한 겹이 자동으로 제거된다는 점에서 약간 달라요:

sub foo(*@args) { say @args.raku }
foo([1, [2, 3], 4], 5, [6, 7]);   # [1, [2, 3], 4, 5, 6, 7]

인자의 구조를 보존하려면 **를 쓸 수 있어요:

sub foo(**@args) { say @args.raku }
foo([1, [2, 3], 4], 5, [6, 7]);  # [[1, [2, 3], 4], 5, [6, 7]]

배열을 인자 집합으로 확장하고 싶을 수도 있어요. Raku에서는 Slip |로 해요:

args = %w(a b c)         # Ruby
foo(*args)
sub foo($q, $r, $s) { ... };
my @args = <a b c>;       # Raku
foo(|@args);

Raku에는 파라미터 전달과 인자 수신의 훨씬 더 고급 방법이 있어요. Signatures와 Captures를 보세요.

트와이질 (Twigils)

Raku는 추가로 "트와이질"을 쓰는데, 변수에 대한 추가 표시자로 시길과 변수 이름의 나머지 사이에 들어가요. 예:

Variable Description
$foo Scalar with no twigil
$!foo Private instance variable
$.foo Instance variable accessor
$*foo Dynamically scoped variable
$^foo A positional (placeholder) parameter to a block
$:foo A named (placeholder) parameter to a block
$=foo POD (documentation) variables
$?FILE Current source filename. The ? twigil indicates a compile-time value
$~foo Sublanguage seen by parser, uncommon

이 예제는 모두 $ 시길을 쓰지만, 대부분 @ (Positional) 나 % (Associative) 도 쓸 수 있어요.

: 심볼

Raku는 Ruby가 심볼을 쓰는 자리에 일반적으로 문자열을 써요. 대표적인 예가 해시 키예요.

address[:joe][:street] # Typical Ruby nested hash with symbol keys
my %address; ...;
%address<joe><street>  # Typical Raku nested hash with string keys

Raku에는 colon-pair 문법이 있는데, 가끔 Ruby 심볼처럼 보여요.

:age            # Ruby symbol
# All of these are equivalent for Raku
:age            ;# Raku pair with implicit True value
:age(True)      ;# Raku pair with explicit True value
age => True     ;# Raku pair using arrow notation
"age" => True   ;# Raku pair using arrow notation and explicit quotes

명시적 값이 없는 colon-pair를 Ruby 심볼인 양 써도 되는 경우가 많겠지만, 그것은 관용적인 Raku가 아니에요.

연산자

많은 연산자가 Ruby와 Raku에서 비슷하게 쓰여요:

  • , List Separator
  • + Numeric Addition
  • - Numeric Subtraction
  • * Numeric Multiplication
  • / Numeric Division
  • % Numeric Modulus
  • ** Numeric Exponentiation
  • ! && || Booleans, high-precedence
  • not and or Booleans, low-precedence

x += 1 대신 $x++를 변수 증가 단축키로 쓸 수 있어요. 전위 증가 ++$x(증가하고 새 값 반환)나 후위 증가 $x++(증가하고 옛 값 반환)로 쓸 수 있어요.

x -= 1 대신 $x--를 변수 감소 단축키로 쓸 수 있어요. 전위 감소 --$x나 후위 감소 $x--로 쓸 수 있어요.

== != < > <= >= 비교

Raku에서 비교는 흔한 오류를 피하려고 숫자와 문자열로 나뉘어요.

  • == != < > <= >= Comparisons
  • eq ne lt gt le ge String comparisons

예를 들어 ==는 값을 숫자로, eq는 문자열로 변환하려 해요.

<=> 3-way 비교

Ruby에서 <=> 연산자는 -1, 0, 1을 돌려줘요. Raku에서는 Order::Less, Order::Same, Order::More를 돌려줘요.

<=>는 비교에 숫자 컨텍스트를 강제해요.

leg ("Less, Equal, or Greater?")는 비교에 문자열 컨텍스트를 강제해요.

cmp는 인자의 기존 타입에 따라 <=> 또는 leg를 해요.

~~ Smartmatch 연산자

Ruby의 ===와 비슷한 아주 흔한 매칭 연산자예요. 예:

my $foo; ...;
say "match!" if $foo ~~ /bar/;       # Regex match
say "match!" if $foo ~~ "bar";       # String match
say "match!" if $foo ~~ :(Int, Str); # Signature match (destructure)

Ruby에서의 등가는 이래요:

foo = "bar"
puts "match 1!" if /bar/ === foo       # Regex match
puts "match 2!" if foo === "bar"       # String match
puts "match 3!" if String === foo      # Class match

이 경우 ===는 대칭이 아니라는 점에 주의하세요. 첫·마지막 경우 변수가 오른쪽에 있어야 해요. Ruby에는 시그니처 클래스에 해당하는 것도 없어요.

이 기능에 대한 더 자세한 내용은 S03/Smartmatching을 보세요.

& | ^ 숫자 비트 연산 / 불리언 연산

Raku에서는 이 단일 문자 연산자가 제거되고, 인자를 필요한 컨텍스트로 강제 변환하는 두 문자 연산자로 대체됐어요.

# Infix ops (two arguments; one on each side of the op)
+&  +|  +^  And Or Xor: Numeric
~&  ~|  ~^  And Or Xor: String
?&  ?|  ?^  And Or Xor: Boolean

# Prefix ops (one argument, after the op)
+^  Not: Numeric
~^  Not: String
?^  Not: Boolean (same as the ! op)

&. 조건부 체이닝 연산자

Ruby는 &. 연산자로, 한 호출이 nil을 돌려줘도 오류 없이 메서드를 체이닝해요. Raku에서는 같은 목적에 .?를 써요.

<< >> 숫자 좌/우 시프트, shovel 연산자

+<+>로 대체됐어요.

puts 42 << 3  # Ruby
say  42 +< 3; # Raku

Ruby는 <<.push와 비슷한 "shovel 연산자"로 자주 쓰는데, Raku에선 이 용법이 흔하지 않아요.

=>: 키-값 구분자

Ruby에서 =>는 Hash 리터럴 선언과 파라미터 전달에서 키/값 쌍의 컨텍스트로 쓰여요. :는 왼쪽이 심볼일 때 축약으로 쓰여요.

Raku에서 =>는 Pair 연산자로, 원리는 꽤 다르지만 많은 상황에서 같게 동작해요.

해시 리터럴에 =>를 쓰면 사용은 아주 비슷해요:

hash = { "AAA" => 1, "BBB" => 2 }  # Ruby, though symbol keys are more common
my %hash = ( AAA => 1, BBB => 2 ); # Raku, uses ()'s though {} usually work

? : 삼항 연산자

Raku에서는 물음표 하나 대신 물음표 두 개, 콜론 하나 대신 느낌표 두 개로 써요. 흔한 삼항 연산자에서 벗어난 이 방식이 여러 상황을 명확히 하고 false 경우를 더 눈에 띄게 해 줘요.

result     = (  score > 60 )  ? 'Pass'  : 'Fail'; # Ruby
my $score; ...;
my $result = ( $score > 60 ) ?? 'Pass' !! 'Fail'; # Raku

+ 문자열 연결

물결표(tilde)로 대체됐어요. 니모닉: 두 문자열을 바늘과 실로 "꿰매는"다고 생각하세요.

$food = 'grape' + 'fruit'  # Ruby
my $food = 'grape' ~ 'fruit'; # Raku

문자열 보간

Ruby에서 "#{foo}s"는 이중 따옴표 문자열에 임베드된 블록을 구분해요. Raku에서는 # 접두사를 빼면 돼요: "{$foo}s". Ruby와 같이 임베드된 블록에 임의 코드를 넣을 수 있고 문자열 컨텍스트로 렌더링돼요.

간단한 변수는 블록 문법 없이 이중 따옴표 문자열로 보간할 수 있어요:

# Ruby
name = "Bob"
puts "Hello! My name is #{name}!"
# Raku
my $name = "Bob";
say "Hello! My name is $name!"

Ruby에서 임베드된 블록의 결과는 .to_s로 문자열 컨텍스트를 얻어요. Raku는 같은 효과에 .Str, 또는 .gist를 써요.

복합 문장

조건문

if elsif else unless

Ruby와 Raku에서 아주 비슷하게 동작하지만, Raku는 { }로 블록을 명확히 구분해요.

# Ruby
if x > 5
    puts "Bigger!"
elsif x == 5
    puts "The same!"
else
    puts "Smaller!"
end
# Raku
my $x; ...;
if $x > 5 {
    say "Bigger!"
} elsif $x == 5 {
    say "The same!"
} else {
    say "Smaller!"
}

조건 표현식을 변수에 바인딩하는 건 조금 달라요:

if x = dostuff(); ...; end   # Ruby
sub dostuff() {...};
if dostuff() -> $x {...}     # Raku, block-assignment uses arrow

unless 조건은 Raku에서 단일 블록만 허용해요. elsifelse 절은 허용하지 않아요.

case-when

Raku의 given-when 구조는 if-elsif-else 체인과 같고, 그 직접적인 대응물은 Ruby의 case-when 구조예요. Ruby가 case-when 표현식의 각 조건에 === (case equality) 연산자를 쓰듯, Raku도 given-when에 smartmatch ~~ 연산자를 써요.

일반 구조:

given EXPR {
    when EXPR { ... }
    when EXPR { ... }
    default { ... }
}

가장 단순한 형태:

my $value; ...;
given $value {
    when "a match" {
        # do-something();
    }
    when "another match" {
        # do-something-else();
    }
    default {
        # do-default-thing();
    }
}

when 문장에서 스칼라 값이 매칭된다는 점에서 단순해요. 더 일반적으로는 매칭이 실제로 입력 값에 대한 smartmatch라서, 스칼라 값 대신 정규식 같은 더 복잡한 개체를 쓸 수 있어요.

루프

while until

대부분 그대로예요. 조건의 괄호는 선택 사항이지만, 쓰면 키워드 바로 뒤에 오면 안 돼요. 그렇지 않으면 함수 호출로 해석되거든요. 조건 표현식을 변수에 바인딩하는 것도 조금 달라요:

while x = dostuff(); ...; end    # Ruby
sub dostuff {...}; ...;
while dostuff() -> $x {...}      # Raku

for .each

Ruby에서 for 루프는 드물고, 대신 열거 가능한 객체에 .each를 써요. Raku로 가장 직접 번역하면 .each.map 둘 다 .map을 쓰는 거지만, 보통은 for 루프를 직접 써요.

# Ruby for loop
for n in 0..5
    puts "n: #{n}"
end

# Ruby, more common usage of .each
(0..5).each do |n|
    puts "n: #{n}"
end
# Raku
for 0..5 -> $n {
    say "n: $n";
}

# Raku, misusing .map
(0..5).map: -> $n {
    say "n: $n";
}

Ruby에서 .each의 반복 변수는 리스트 요소의 복사본이어서, 수정해도 원본 리스트에 아무 영향이 없어요. 그것은 REFERENCE의 복사본이므로, 가리키는 값은 여전히 바꿀 수 있다는 점을 기억하세요.

Raku에서 그 별칭은 (안전을 위해) 읽기 전용이라 Ruby와 정확히 같게 동작해요. -><->로 바꾸지 않는 한이죠.

cars.each { |car| ... }    # Ruby; read-only reference
my @cars; ...;
for @cars  -> $car   {...} # Raku; read-only
for @cars <-> $car   {...} # Raku; read-write

흐름 중단 문장

Ruby와 같아요:

  • next
  • redo

Ruby의 break는 Raku에서 last예요.

정규식 (regex / regexp)

Raku의 정규식은 Ruby보다 상당히 다르고 더 강력해요. 예를 들어 기본적으로 공백이 무시되고 모든 문자가 이스케이프되어야 해요. 정규식은 효율적인 그래머를 만드는 방식으로 쉽게 결합·선언될 수 있어요.

Raku 정규식에는 강력한 기능이 많은데, 특히 같은 문법으로 전체 그래머를 정의하는 거예요. Regexes와 Grammars를 보세요.

.match 메서드와 =~ 연산자

Ruby에서 정규식 매칭은 =~ 정규식 매칭 연산자나 .match 메서드로 할 수 있어요. Raku에서는 ~~ smartmatch 연산자나 .match 메서드를 대신 써요.

next if line   =~ /static/   # Ruby
next if line  !~  /dynamic/; # Ruby
next if line.match(/static/) # Ruby
my $line; ...;
next if $line  ~~ /static/;    # Raku
next if $line !~~ /dynamic/ ;  # Raku
next if $line.match(/static/); # Raku

또는 .match.subst 메서드를 쓸 수 있어요. .subst는 비변형(non-mutating)이라는 점에 주의하세요. S05/Substitution을 보세요.

.sub.sub!

Raku에서는 보통 s/// 연산자로 정규식 치환을 해요.

fixed = line.sub(/foo/, 'bar')        # Ruby, non-mutating
my $line; ...;
my $fixed = $line.subst(/foo/, 'bar') # Raku, non-mutating
line.sub!(/foo/, 'bar')   # Ruby, mutating
my $line; ...;
$line ~~ s/foo/bar/;      # Raku, mutating

정규식 옵션

정규식 끝의 옵션을 시작으로 옮겨요. /abc/ 같은 평범한 매치에 선택적 m을 추가해야 할 수도 있어요.

next if $line =~    /static/i # Ruby
my $line; ...;
next if $line ~~ m:i/static/; # Raku

공백은 무시되고, 대부분 따옴표 처리해야 함

가독성과 재사용을 돕기 위해 Raku 정규식에서는 공백이 의미를 갖지 않아요.

/this is a test/ # Ruby, boring string
/this.*/         # Ruby, possibly interesting string
/ this " " is " " a " " test /; # Raku, each space is quoted
/ "this is a test" /;           # Raku, quoting the whole string
/ this .* /;                    # Raku, possibly interesting string

특수 매처는 일반적으로 <> 문법에 속함

Raku 정규식이 지원하는 특수 매칭 문법이 많아요. 전부 나열하진 않겠지만, 종종 ()에 둘러싸인 대신 <>에 둘러싸여요.

문자 클래스의 경우:

  • [abc]<[abc]>
  • [^abc]<-[abc]>
  • [a-zA-Z]<[a..zA..Z]>
  • [[:upper:]]<:upper>
  • [abc[:upper:]]<[abc]+:Upper>

Look-around 단언:

  • (?=[abc])<?[abc]>
  • (?=ar?bitrary* pattern)<before ar?bitrary* pattern>
  • (?!=[abc])<![abc]>
  • (?!=ar?bitrary* pattern)<!before ar?bitrary* pattern>
  • (?<=ar?bitrary* pattern)<after ar?bitrary* pattern>
  • (?<!ar?bitrary* pattern)<!after ar?bitrary* pattern>

(<> 문법과 무관하게 "lookaround" /foo\Kbar//foo <( bar )> /가 돼요.)

  • (?(?{condition))yes-pattern|no-pattern)[ <?{condition}> yes-pattern | no-pattern ]

Longest token matching (LTM)이 대안(alternation)을 대체

Raku 정규식에서 |는 Longest Token Match (LTM)를 하는데, 정규식에 먼저 쓰였는지가 아니라 규칙 집합에 기반해 모호한 매치에서 어느 대안이 이길지 정해요.

새 로직을 피하려면 Ruby 정규식의 |를 전부 ||로 바꿔요.

파일 관련 연산

텍스트 파일의 줄을 배열로 읽기

Ruby와 Raku 모두 파일의 모든 줄을 한 변수로 쉽게 읽을 수 있고, 두 경우 모두 각 줄에서 줄바꿈이 제거돼요.

lines = File.readlines("file")   # Ruby
my @lines = "file".IO.lines;     # Raku, create an IO object from a string

텍스트 파일의 줄 반복

전체 파일을 메모리에 읽는 건 권장되지 않아요. Raku의 .lines 메서드는 lazy 시퀀스를 돌려주지만, 배열에 할당하면 파일을 읽도록 강제해요. 결과를 반복하는 게 더 좋아요:

# Ruby
File.foreach("file") do |line|
    puts line
end
# Raku
for "file".IO.lines -> $line {
    say $line
}

객체 지향

기본 클래스, 메서드, 속성

클래스는 Ruby와 Raku에서 class 키워드로 비슷하게 정의돼요. Ruby는 메서드에 def를 쓰고, Raku는 method를 써요.

# Ruby
class Foo
    def greet(name)
        puts "Hi #{name}!"
    end
end
# Raku
class Foo {
    method greet($name) {
        say "Hi $name!"
    }
}

Ruby에서는 미리 선언하지 않고 속성을 쓸 수 있고, @ 시길 때문에 속성임을 알 수 있어요. attr_accessor와 그 변형으로 접근자를 쉽게 만들 수 있어요. Raku에서는 has 선언과 다양한 시길을 써요. 개인 속성에는 ! 트와이질을, 접근자를 만들려면 .를 쓸 수 있어요.

# Ruby
class Person
    attr_accessor :age    # Declare .age as an accessor method for @age
    def initialize
        @name = 'default' # Assign default value to private instance var
    end
end
# Raku
class Person {
    has $.age;              # Declare $!age and accessor methods
    has $!name = 'default'; # Assign default value to private instance var
}

클래스의 새 인스턴스를 만드는 데 .new 메서드를 써요. Ruby에서는 initialize 안에서 필요에 따라 인스턴스 변수를 수동으로 할당해야 해요. Raku에서는 접근자 속성의 키/값 쌍을 받는 기본 생성자를 얻고, TWEAK 메서드에서 추가 설정을 할 수 있어요. Ruby처럼 더 고급 기능을 위해 new 자체를 오버라이드할 수도 있지만 드물어요.

# Ruby
class Person
    attr_accessor :name, :age
    def initialize(attrs)
        @name = attrs[:name] || 'Jill'
        @age  = attrs[:age] || 42
        @birth_year = Time.now.year - @age
    end
end
p = Person.new( name: 'Jack', age: 23 )
# Raku
class Person {
    has $.name = 'Jill';
    has $.age  = 42;
    has $.birth_year;
    method TWEAK {
        $!birth_year = now.Date.year - $!age;
    }
}
my $p = Person.new( name => 'Jack', age => 23 )

개인 메서드

Raku의 개인 메서드는 이름에 ! 접두사를 붙여 선언하고, . 대신 !로 호출해요.

# Ruby
class Foo
    def visible
        puts "I can be seen!"
        hidden
    end

    private
    def hidden
        puts "I cannot easily be called!"
    end
end
# Raku
class Foo {
    method visible {
        say "I can be seen!";
        self!hidden;
    }

    method !hidden {
        say "I cannot easily be called!";
    }
}

중요한 점은 Ruby에서 자식 객체가 부모의 개인 메서드를 볼 수 있다는 거예요 (그래서 다른 언어의 "protected" 메서드에 더 가까워요). Raku에서 자식 객체는 부모의 개인 메서드를 호출할 수 없어요.

메타로 가기

메타프로그래밍 예제 몇 개를 볼게요. Raku는 메타메서드를 일반 메서드와 캐럿으로 구분한다는 점에 주목하세요.

# Ruby
person = Person.new
person.class
person.methods
person.instance_variables
# Raku
class Person {};
...
my $person = Person.new;
$person.^name;             # Raku, returns Person (class)
$person.^methods;          # Raku, using .^ syntax to access metamethods
$person.^attributes;

Ruby처럼 Raku에서도 모든 것이 객체이지만, 모든 연산이 .public_send와 같지는 않아요. 많은 연산자가 typed multi-dispatch(타입 있는 함수 시그니처)를 사용해서 어떤 구현을 쓸지 정하는 전역 함수예요.

5.public_send(:+, 3)    # => 8, Ruby
&+       # => 8, Raku, reference to infix addition operator
&[+].^candidates # Raku, lists all signatures for the + operator

자세한 내용은 Metaobject Protocol을 보세요.

환경 변수

Raku 모듈 라이브러리 경로

Ruby에서 모듈용 추가 검색 경로를 지정하는 환경 변수 중 하나가 RUBYLIB예요.

$ RUBYLIB="/some/module/lib" ruby program.rb

Raku에서는 비슷한데 이름만 바꾸면 돼요. 짐작했겠지만 RAKULIB를 쓰면 돼요:

$ RAKULIB="/some/module/lib" raku program.raku

Ruby와 같이 RAKULIB를 지정하지 않으면 프로그램 안에서 use lib 프라그마로 라이브러리 경로를 지정해야 해요:

# Ruby and Raku
use lib '/some/module/lib';

기타

모듈에서 특정 함수 가져오기

Ruby에는 모듈에서 메서드를 선택적으로 가져오거나 내보낼 내장 방법이 없어요.

Raku에서는 관련 sub에 is export 역할을 붙여 내보낼 함수를 지정하고, 이 역할이 있는 모든 sub가 내보내져요. 그래서 다음 모듈 Bar는 sub foobar를 내보내지만 baz는 내보내지 않아요:

unit module Bar; # remainder of the file is in module Bar { ... }
sub foo($a) is export { say "foo $a" }
sub bar($b) is export { say "bar $b" }
sub baz($z) { say "baz $z" }

이 모듈을 쓰려면 그저 use Bar하면 함수 foobar를 쓸 수 있어요:

use Bar;
foo(1);    #=> "foo 1"
bar(2);    #=> "bar 2"

baz를 쓰려 하면 컴파일 타임에 "Undeclared routine" 오류가 나요.

일부 모듈은 함수를 선택적으로 가져오게 허용하는데, 이렇게 생겼어요:

use Bar <foo>; # Import only foo
foo(1);        #=> "foo 1"
bar(2);        # Error!

OptionParser, 명령줄 플래그 파싱

Raku에서 명령줄 인자 스위치 파싱은 MAIN 서브루틴의 파라미터 목록으로 돼요.

# Ruby
require 'optparse'
options = {}
OptionParser.new do |opts|
    opts.banner = 'Usage: example.rb --length=abc'
    opts.on("--length", "Set the file") do |length|
        raise "Length must be > 0" unless length.to_i > 0
        options[:length] = length
    end
    opts.on("--filename", "Set the file") do |filename|
        options[:file] = filename
    end
    opts.on("--verbose", "Increase verbosity") do |verbose|
        options[:verbose] = true
    end
end.parse!

puts options[:length]
puts options[:filename]
puts 'Verbosity ', (options[:verbose] ? 'on' : 'off')
ruby example.rb --filename=foo --length=42 --verbose
    42
    foo
    Verbosity on

ruby example.rb --length=abc
    Length must be > 0
# Raku
sub MAIN ( Int :$length where * > 0, :$filename = 'file.dat', Bool :$verbose ) {
    say $length;
    say $filename;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}
raku example.raku --file=foo --length=42 --verbose
    42
    foo
    Verbosity on
raku example.raku --length=abc
    Usage:
      example.raku [--length=<Int>] [--file=<Any>] [--verbose]

Raku는 명령줄 파싱 오류 시 전체 usage 메시지를 자동 생성한다는 점에 주목하세요.

RubyGems, 외부 라이브러리

점점 더 많은 Raku 라이브러리와 그것을 관리하는 도구가 제공되는 https://raku.land/ 를 보세요.

사용하던 모듈이 Raku로 전환되지 않았고, 이 문서에 대안이 없다면, Raku에서의 사용이 아직 다뤄지지 않았을 수 있어요.