Python to Raku — 한눈에

Python to Raku — 한눈에 (Nutshell)

Python에 익숙한 사람이 Raku를 배울 때, '이걸 Raku에서는 어떻게 쓰지?' 하는 순간이 수없이 많아요. 이 페이지는 Python 배경지식이 있는 사람을 위해 Raku에서 Python 구조·관용구에 해당하는 문법을 짚어주는 참고서예요.

출처: Raku 공식 문서 — Python to Raku: nutshell

본문

이 페이지는 Python 배경의 사람들이 Raku를 배울 수 있는 길을 제공하려는 시도예요. 여러 Python 구성과 관용구에 대해 Raku의 동등 문법을 다뤄요.

기본 문법 (Basic syntax)

Hello, world

"Hello, world!" 출력부터 시작할게요. Raku의 키워드는 Python의 에 해당해요. Python 2처럼 괄호는 선택적이에요. 줄 끝에는 개행이 추가돼요.

Python 2

print "Hello, world!"

Python 3

print("Hello, world!")

Raku

put "Hello, world!"

비슷하게 동작하지만 인자의 gist 메서드를 호출하는 키워드도 있어요.

Raku

my $hello = "Hello, world!";
say $hello;  # 역시 "Hello, world!" 출력
             # put $hello.gist 와 동일

Python에서 '"는 서로 바꿔 쓸 수 있어요. Raku에서는 둘 다 따옴표로 쓸 수 있지만, 이중 따옴표(")는 보간이 수행됨을 나타내요. 예를 들어 $로 시작하는 변수와 중괄호에 든 표현식이 보간돼요.

Raku

my $planet = 'earth';
say "Hello, $planet";                 # OUTPUT: «Hello, earth␤»
say 'Hello, $planet';                 # OUTPUT: «Hello, $planet␤»
say "Hello, planet number { 1 + 2 }"; # OUTPUT: «Hello, planet number 3␤»

문장 구분자 (Statement separators)

Python에서 개행은 문장의 끝을 나타내요. 예외가 몇 가지 있어요: 개행 앞의 백슬래시는 문장을 여러 줄로 계속시켜요. 또한 짝이 안 맞는 여는 괄호·대괄호·중괄호가 있으면 짝이 맞는 괄호가 닫힐 때까지 문장이 여러 줄로 계속돼요.

Raku에서는 세미콜론이 문장의 끝을 나타내요. 세미콜론은 블록의 마지막 문장이면 생략할 수 있어요. 닫는 중괄호 다음에 개행이 오는 경우에도 세미콜론을 생략할 수 있어요.

Python

print 1 + 2 + \
    3 + 4
print ( 1 +
    2 )

Raku

say 1 + 2 +
    3 + 4;
say 1 +
    2;

블록 (Blocks)

Python에서 들여쓰기가 블록을 나타내요. Raku는 중괄호를 사용해요.

Python

if 1 == 2:
    print("Wait, what?")
else:
    print("1 is not 2.")

Raku

if 1 == 2 {
    say "Wait, what?"
} else {
    say "1 is not 2."
}

위에서 보듯 조건식에서 괄호는 두 언어 모두 선택적이에요.

변수 (Variables)

Python에서 변수는 선언과 초기화를 동시에 해요:

foo = 12
bar = 19

Raku에서 my 선언자는 어휘 변수를 선언해요. 변수는 =로 초기화할 수 있어요. 먼저 선언하고 나중에 초기화하거나, 선언과 동시에 초기화할 수 있어요.

my $foo;       # 선언
$foo = 12;     # 초기화
my $bar = 19;  # 한 번에 둘 다

또한 눈치챘겠지만, Raku의 변수는 보통 시길(sigil)로 시작해요. 시길은 컨테이너의 타입을 나타내는 기호예요. $로 시작하는 변수는 스칼라를 담아요. @로 시작하는 변수는 배열, %로 시작하는 변수는 해시(dict)를 담아요. \로 선언하고 사용할 때는 시길 없이 쓰는 시길리스(sigilless) 변수는 할당된 값에 바인딩되어 있어서 불변(immutable)이에요.

이제부터 대부분의 예시에서 Python과의 유사성을 보여주려고 시길리스 변수를 쓸 거예요. 기술적으로 맞지만, 일반적으로는 그 불변성(또는 시그니처에서 쓰일 때 타입 독립성)이 필요하거나 강조되어야 할 곳에 시길리스 변수를 쓸 거예요.

Python

s = 10
l = [1, 2, 3]
d = { 'a' : 12, 'b' : 99 }

print(s)
print(l[2])
print(d['a'])
# 10, 3, 12

Raku

my $s = 10;
my @l = 1, 2, 3;
my %d = a => 12, b => 99;
my \x = 99;

say $s;
say @l[1];
say %d<a>;  # 또는 %d{'a'}
say x;
# 10, 2, 12, 99

스코프 (Scope)

Python에서 함수와 클래스는 새 스코프를 만들지만, 다른 블록 구성자(예: 루프, 조건문)는 스코프를 만들지 않아요. Python 2에서 리스트 컴프리헨션은 새 스코프를 만들지 않지만, Python 3에서는 만든다.

Raku에서는 모든 블록이 어휘 스코프를 만들어요.

Python

if True:
    x = 10
print(x)
# x is now 10

Raku

if True {
    my $x = 10
}
say $x
# error, $x is not declared in this scope
my $x;
if True {
    $x = 10
}
say $x
# ok, $x is 10

Python

x = 10
for x in 1, 2, 3:
   pass
print(x)
# x is 3

Raku

my \x = 10;
for 1, 2, 3 -> \x {
    # 아무것도 하지 않기
}
say x;
# x is 10

Python의 람다는 Raku에서 블록이나 포인티 블록(->)으로 쓸 수 있어요.

Python

l = lambda i: i + 12

Raku

my $l = -> $i { $i + 12 }

람다를 구성하는 또 다른 Raku 관용구는 Whatever 스타 *예요.

Raku

my $l = * + 12    # 위와 동일

표현식의 *는 인자의 자리표시자가 되어 컴파일 시간에 표현식을 람다로 바꿔요. 표현식의 각 *는 별도의 위치 파라미터예요.

서브루틴과 블록에 대한 더 많은 구조는 아래 섹션을 보세요.

Python 의 다른 예:

Python

squares = []
for x in range(5):
    squares.append(lambda: x ** 2)
print(squares[2]())
print(squares[4]())
# both 16 since there is only one x

Raku

my \squares = [];
for ^5 -> \x {
    squares.append({ x² });
}
say squares[2]();
say squares[4]();
# 4, 16 since each loop iteration has a lexically scoped x,

^Nrange(N)과 비슷하다는 점에 주의하세요. 마찬가지로 N..^Mrange(N, M)처럼 동작해요 (N에서 M-1까지의 리스트). 범위 N..M은 N에서 M까지의 리스트예요. .. 전이나 후의 ^는 리스트의 시작점이나 끝점(또는 둘 다)이 제외되어야 함을 나타내요.

또한 x ** 2(이것도 잘 동작해요)를 쓰는 귀여운 방법이에요. 유니코드 위첨자 2는 숫자를 제곱해요. 다른 많은 유니코드 연산자도 예상대로 동작하지만(지수, 분수, π), Raku에서 쓸 수 있는 모든 유니코드 연산자·기호에는 ASCII 동등 형태가 있어요.

제어 흐름 (Control flow)

Python에는 for 루프와 while 루프가 있어요:

for i in 1, 2:
    print(i)
j = 1
while j < 3:
    print(j)
    j += 1

# 1, 2, 1, 2

Raku에도 for 루프와 while 루프가 있어요:

for 1, 2 -> $i {
    say $i
}
my $j = 1;
while $j < 3 {
    say $j;
    $j += 1
}

(Raku에는 몇 가지 더 많은 루프 구성도 있어요: repeat...until, repeat...while, until, loop.)

Raku의 last는 루프를 떠나며, Python의 break에 해당해요. Python의 continue는 Raku의 next예요.

Python

for i in range(10):
    if i == 3:
        continue
    if i == 5:
        break
    print(i)

Raku

for ^10 -> $i {
    next if $i == 3;
    last if $i == 5;
    say $i;
}

위처럼 if를 문장 수식어로 쓰는 것은 리스트 컴프리헨션 밖에서도 Raku에서 허용돼요.

Python for 루프 안의 yield 문(제너레이터를 만드는)은 Raku의 gather/take 구성과 비슷해요. 이 둘은 모두 1, 2, 3을 출력해요.

Python

def count():
    for i in 1, 2, 3:
        yield i

for c in count():
    print(c)

Raku

sub count {
    gather {
        for 1, 2, 3 -> $i {
            take $i
        }
    }
}

for count() -> $c {
    say $c;
}

Python의 enumerate().items() 메커니즘으로 리스트나 dict/map을 반복하는 사용은 둘 다 Raku에서 같은 메서드로 달성할 수 있어요 (리스트의 "key"가 배열류 숫자 인덱스이기 때문이에요):

Python

elems = ["neutronium", "hydrogen", "helium", "lithium"]
for i, e in enumerate(elems):
    print("Elem no. %d is %s" % (i, e))
symbols = ["n", "H", "He", "Li"]
elem4Symbol = {s: e for s, e in zip(symbols, elems)}
for symbol, elem in elem4Symbol.items():
    print("Symbol '%s' stands for %s" % (symbol, elem))
# Elem no. 0 is neutronium
# Elem no. 1 is hydrogen
# Elem no. 2 is helium
# Elem no. 3 is lithium
# Symbol 'H' stands for hydrogen
# Symbol 'He' stands for helium
# Symbol 'Li' stands for lithium
# Symbol 'n' stands for neutronium

Raku

my @elems = <neutronium hydrogen helium lithium>;
for @elems.kv -> $i, $e {
    say "Elem no. $i is $e"
}

my @symbols = <n H He Li>;
my %elem-for-symbol;
%elem-for-symbol{@symbols} = @elems;

# 반복 순서는 Python과 다를 수 있음에 주의
for %elem-for-symbol.kv -> $symbol, $element {
    say "Symbol '$symbol' stands for $element";
}

람다, 함수, 서브루틴

Python의 def로 함수(서브루틴)를 선언하는 것은 Raku에서 sub로 합니다.

def add(a, b):
    return a + b
sub add(\a, \b) {
    return a + b
}

return은 선택적이에요. 마지막 표현식의 값이 반환값으로 쓰여요:

sub add(\a, \b) {
    a + b
}
# 시길을 가진 변수 사용
sub add($a, $b) {
    $a + $b
}

Python 2 함수는 위치 인자나 키워드 인자로 호출할 수 있어요. 이는 호출자가 결정해요. Python 3에서는 일부 인자가 "keyword only"일 수 있어요. Raku에서는 위치·명명 인자가 루틴의 시그니처에 의해 결정돼요.

Python

def speak(word, times):
    for i in range(times):
        print word
speak('hi', 2)
speak(word='hi', times=2)

Raku

위치 파라미터:

sub speak($word, $times) {
  say $word for ^$times
}
speak('hi', 2);

명명 파라미터는 콜론으로 시작해요:

sub speak(:$word, :$times) {
  say $word for ^$times
}
speak(word => 'hi', times => 2);
speak(:word<hi>, :times<2>);      # 대안, 더 관용적

Raku는 다중 디스패치를 지원하므로, 루틴을 multi로 선언하면 여러 시그니처를 제공할 수 있어요.

multi speak($word, $times) {
  say $word for ^$times
}
multi speak(:$word, :$times) {
    speak($word, $times);
}
speak('hi', 2);
speak(:word<hi>, :times<2>);

명명 파라미터는 다양한 형식으로 보낼 수 있어요:

sub hello {...};
# 전부 동일
hello(name => 'world'); # fat arrow 문법
hello(:name('world'));  # pair 생성자
hello :name<world>;     # <> 가 말을 인용해 리스트를 만듦
my $name = 'world';
hello(:$name);          # 같은 이름의 어휘 변수

익명 함수는 sub, 블록, 포인티 블록으로 만들 수 있어요.

Python

square = lambda x: x ** 2

Raku

my $square = sub ($x) { $x ** 2 };  # 익명 sub
my $square = -> $x { $x ** 2 };     # 포인티 블록
my $square = { $^x ** 2 };          # 자리표시자 변수
my $square = { $_ ** 2 };           # topic 변수

자리표시자 변수는 위치 파라미터를 형성하도록 사전순으로 정렬돼요. 그래서 다음은 같아요:

my $power = { $^x ** $^y };
my $power = -> $x, $y { $x ** $y };

리스트 컴프리헨션 (List comprehensions)

후위 문장 수식어와 블록을 결합하면 Raku에서 리스트 컴프리헨션을 쉽게 만들 수 있어요.

Python

print([ i * 2 for i in [3, 9]])                      # OUTPUT: «[6, 18]␤»

Raku

say ( $_ * 2 for 3, 9 );                           # OUTPUT: «(6 18)␤»
say ( { $^i * 2 } for 3, 9 );                      # OUTPUT: «(6 18)␤»
say ( -> \i { i * 2 } for 3, 9 );                  # OUTPUT: «(6 18)␤»

조건을 적용할 수 있지만, Python에서 if가 두 번째에 오는 것과 달리 Raku에서는 if 키워드가 먼저 와요.

print [ x * 2 for x in [1, 2, 3] if x > 1 ]          # OUTPUT: «[4, 6]␤»

vs

say ( $_ * 2 if $_ > 1 for 1, 2, 3 );              # OUTPUT: «(4 6)␤»

중첩 루프에는 크로스 프로덕트 연산자 X가 도움이 돼요:

print([ i + j for i in [3,9] for j in [2,10] ])       # OUTPUT: «[5, 13, 11, 19]␤»

다음 중 하나가 됩니다:

say ( { $_[0] + $_[1] } for (3,9) X (2,10) );      # OUTPUT: «(5 13 11 19)␤»
say ( -> (\i, \j) { i + j } for (3,9) X (2,10) );  # OUTPUT: «(5 13 11 19)␤»
say ( -> ($i, $j) { $i + $j } for (3,9) X (2,10) );# OUTPUT: «(5 13 11 19)␤»
say ( { $^a[0] + $^a[1] } for (3,9) X (2,10) );    # OUTPUT: «(5 13 11 19)␤»

Python의 map처럼 동작하는 map과 Python의 filter처럼 동작하는 grep을 쓰는 것도 대안이에요.

클래스와 객체 (Classes and objects)

여기 Python 의 예가 있어요. 먼저 Raku에서 속성(attribute)으로 알려진 "인스턴스 변수"를 살펴볼게요:

Python:

class Dog:
    def __init__(self, name):
        self.name = name

Raku:

class Dog {
    has $.name;
}

생성된 각 클래스에 대해 Raku는 명명 인자를 받는 생성자 메서드 new를 기본으로 제공해요.

Python:

d = Dog('Fido')
e = Dog('Buddy')
print(d.name)
print(e.name)

Raku

my $d = Dog.new(:name<Fido>); # 또는: Dog.new(name => 'Fido')
my $e = Dog.new(:name<Buddy>);
say $d.name;
say $e.name;

Raku의 클래스 속성은 몇 가지 방법으로 선언할 수 있어요. 한 방법은 어휘 변수와 그에 접근하는 메서드를 선언하는 거예요.

Python:

class Dog:
    kind = 'canine'                # class attribute
    def __init__(self, name):
        self.name = name           # instance attribute
d = Dog('Fido')
e = Dog('Buddy')
print(d.kind)
print(e.kind)
print(d.name)
print(e.name)

Raku:

class Dog {
    my $kind = 'canine';           # class attribute
    method kind { $kind }
    has $.name;                    # instance attribute
}

my $d = Dog.new(:name<Fido>);
my $e = Dog.new(:name<Buddy>);
say $d.kind;
say $e.kind;
say $d.name;
say $e.name;

Raku에서 속성을 변경하려면 속성에 is rw 트레잇을 사용해야 해요:

Python:

class Dog:
    def __init__(self, name):
        self.name = name
d = Dog()
d.name = 'rover'

Raku:

class Dog {
    has $.name is rw;
}
my $d = Dog.new;
$d.name = 'rover';

상속은 is로 해요:

Python

class Animal:
    def jump(self):
        print ("I am jumping")

class Dog(Animal):
    pass

d = Dog()
d.jump()

Raku

class Animal {
    method jump {
        say "I am jumping"
    }
}

class Dog is Animal {
}

my $d = Dog.new;
$d.jump;

다중 상속은 is 트레잇을 필요한 만큼 사용해서 가능해요. 또는 also 키워드와 함께 사용할 수도 있어요.

Python

class Dog(Animal, Friend, Pet):
    pass

Raku

class Animal {}; class Friend {}; class Pet {};
...;
class Dog is Animal is Friend is Pet {};

또는

class Animal {}; class Friend {}; class Pet {};
...;
class Dog is Animal {
    also is Friend;
    also is Pet;
    ...
}

데코레이터 (Decorators)

Python의 데코레이터는 함수를 다른 함수로 감싸는 방법이에요. Raku에서는 wrap으로 해요.

Python

def greeter(f):
    def new():
        print('hello')
        f()
    return new

@greeter
def world():
    print('world')

world();

Raku

sub world {
    say 'world'
}

&world.wrap(sub () {
    say 'hello';
    callsame;
});

world;

대안으로 트레잇을 쓸 수도 있어요:

# 'greeter' 트레잇 선언
multi trait_mod:<is>(Routine $r, :$greeter) {
    $r.wrap(sub {
        say 'hello';
        callsame;
    })
}

sub world is greeter {
    say 'world';
}

world;

컨텍스트 매니저 (Context managers)

Python의 컨텍스트 매니저는 스코프에 진입하거나 나올 때 일어나는 동작을 선언해요.

'hello', 'world', 'bye' 문자열을 출력하는 Python 컨텍스트 매니저가 여기 있어요.

class hello:
    def __exit__(self, type, value, traceback):
        print('bye')
    def __enter__(self):
        print('hello')

with hello():
    print('world')

"enter"와 "exit" 이벤트에 대해 블록을 인자로 전달하는 것이 한 가지 옵션이에요:

sub hello(Block $b) {
    say 'hello';
    $b();
    say 'bye';
}

hello {
    say 'world';
}

관련 아이디어로 블록에 진입하거나 나올 때 실행되도록 설정할 수 있는 ''가 있어요.

{
    LEAVE say 'bye';
    ENTER say 'hello';
    say 'world';
}

input

Python 3에서 input 키워드는 사용자에게 프롬프트를 표시하는 데 쓰여요. 이 키워드에 선택적 인자를 제공할 수 있는데, 이는 후행 개행 없이 표준 출력에 기록돼요:

user_input = input("Say hi → ")
print(user_input)

프롬프트가 표시되면 Hi나 다른 문자열을 입력할 수 있고, user_input 변수에 저장돼요. 이것은 Raku의 와 비슷해요:

my $user_input = prompt("Say hi → ");
say $user_input; # OUTPUT: 입력한 무엇이든.

튜플 (Tuples)

Python 튜플은 불변 시퀀스예요. 시퀀스 요소가 같은 타입일 필요는 없어요.

Python

tuple1 = (1, "two", 3, "hat")
tuple2 = (5, 6, "seven")
print(tuple1[1])                                   # OUTPUT: «two␤»
tuple3 = tuple1 + tuple2
print(tuple3)                                      # OUTPUT: «(1, 'two', 3, 'hat', 5, 6, 'seven')␤»

Raku

Raku에는 내장 Tuple 타입이 없어요. Raku에서 List 타입으로 같은 동작을 얻거나, 외부 모듈에서 얻을 수 있어요.

my $list1 = (1, "two", 3, "hat");
my $list2 = (5, 6, "seven");
say $list1[1];                                 # OUTPUT: «two␤»
my $list3 = (slip($list1), slip($list2));
my $list4 = (|$list1, |$list2);                # 앞줄과 동등
say $list3;                                    # OUTPUT: «(1, two, 3, hat, 5, 6, seven)␤»

더 알아보기 (Learn more)