slangs — Raku의 하위 언어와 슬랭 확장

slangs — Raku의 하위 언어와 슬랭 확장

Raku 커뮤니티에서 "Slang"이라는 용어는 두 가지 다른 의미로 쓰여요. 하나는 Raku 안의 하위 언어(quoting 하위 언어, regex 하위 언어 같은)를 가리키고, 다른 하나는 언어의 그래머를 수정하는 슬랭 모듈을 추가해서 만든 Raku의 변형을 가리켜요. 이 페이지에서는 둘을 모두 다룰게요.

출처: Slangs - Raku Documentation

Raku 안의 하위 언어

~ 트와이질(twigil)은 하위 언어(slang)를 가리키는 데 쓰여요. 유용한 것들은 다음과 같아요:

표기 의미
$~MAIN 현재 메인 언어 (예: Raku 문장)
$~Quote quoting 언어의 현재 루트
$~Quasi quasiquoting 언어의 현재 루트
$~Regex regex 언어의 현재 루트
$~Trans transliteration 언어의 현재 루트
$~P5Regex Perl regex 언어의 현재 루트

이 언어들은 현재 렉시컬 스코프에서 augment 할 수 있어요.

use MONKEY-TYPING;
augment slang Regex {  # derive from $~Regex and then modify $~Regex
    token backslash:std<\Y> { YY };
}

MONKEY-TYPING 프라그마는 augment 키워드 사용에 필요한 전제 조건이에요. 뒤따르는 코드가 보통 권장되지 않는 유형이라는 걸 상기시키는 역할이라고 생각하면 돼요. augment에 대한 자세한 내용은 augment 선언자 문서에 있어요.

언어를 수정하는 슬랭

기존 슬랭들

많은 슬랭이 zef를 통해 모듈로 제공돼요. 이들은 이럴 때 유용해요.

  • 직접 사용하기 — 나만의 슬랭을 쓸 필요 없이
  • 예시로 — 나만의 슬랭을 쓸 때 영감을 얻기 위해

기존 Slang 모듈을 보려면 Slang 태그가 붙은 모든 것이나 slang 검색 결과를 확인하면 돼요.

슬랭을 피하는 법

슬랭을 쓰는 건 아름답고 강력한 도구지만, 때로는 "모기 잡으려고 화염방사기를 쓸" 필요는 없어요. 가끔은 더 단순한 도구로 슬랭 비슷한 걸 해낼 수 있는지 먼저 확인하는 게 좋아요.

슬랭을 만들기 전에 이런 대안들을 고려해 봐요:

  • 커스텀 연산자(Custom Operators) — prefix, infix, postfix, circumfix, postcircumfix 연산자를 정의할 수 있어요. 코드 전체에서 쓸 수 있고, 연산을 위한 새 구문 구조만 필요하다면 대개 이것으로 충분해요.
  • 커스텀 트레잇(Custom Traits, trait_mod로 만듦) — 선언에 컴파일 타임 동작을 더할 수 있어요. 그래머를 바꾸지 않고 클래스, 루틴, 변수 등을 수정할 수 있어요.
  • 커스텀 선언자(Custom Declarators, Red ORM의 model 선언자 같은)classsub 같은 내장 선언자와 비슷하게 동작하는 새 키워드를 만들 수 있어요. 약간 고급이지만 완전한 슬랭을 만드는 것보다는 덜 복잡해요. 보통 그래머를 더 집중된 수준에서 다루니까요.

커스텀 선언자 만들기

class, role, sub 같은 내장 선언자처럼 동작하는 커스텀 선언자를 만드는 가장 직접적인 방법은 EXPORTHOW::DECLARE 메커니즘을 쓰는 거예요. Red ORM이 model 선언자에 쓴 접근법이 바로 이거예요.

EXPORTHOW::DECLARE 사용하기

EXPORTHOW::DECLARE 메커니즘은 컴파일러가 선언자 키워드를 만났을 때 사용할 커스텀 HOW(Higher Order Workings) 클래스를 등록할 수 있게 해 줘요. 완전한 예제를 볼게요:

# lib/DeclaratorOne.rakumod
# Custom declarator-one implementation
# Similar to how Red ORM implements the "model" declarator

class MetamodelX::DeclaratorOne is Metamodel::ClassHOW {}

my package EXPORTHOW {
    package DECLARE {
        constant declarator-one = MetamodelX::DeclaratorOne;
    }
}

이 선언자를 쓰려면:

use DeclaratorOne;

declarator-one Foo {
    has $.name;
    method greet { say "Hello from $.name()!" }
}

my $foo = Foo.new(name => "World");
$foo.greet;  # OUTPUT: «Hello from World!␤»

동작 방식은 이래요:

  • 적절한 기본 HOW(클래스형 선언자는 Metamodel::ClassHOW, 역할형 선언자는 Metamodel::ParametricRoleHOW)를 확장한 HOW 클래스를 만들어요.
  • EXPORTHOW::DECLARE 패키지로 내보내는데, 상수 이름이 원하는 선언자 키워드와 일치해야 해요.
  • 컴파일러가 선언자 키워드를 만나면, class 선언에 Metamodel::ClassHOW를 쓰는 것처럼 그 HOW로 타입을 구성해요.

이 접근법은 Raku의 그래머와 타입 시스템에 자연스럽게 통합되어, 커스텀 선언자가 내장 선언자처럼 정확히 동작하게 해요. 실제 사례는 Red ORM이 소스 코드에서 model 선언자를 구현한 방식을 보면 돼요.

커스텀 속성 클래스

has로 속성이 만들어질 때 사용될 클래스도 커스텀할 수 있어요. 이것은 EXPORTHOW::DECLARE로 커스텀 속성 클래스를 내보내면 되는데, 클래스 선언자가 HOW 클래스를 확장하는 것과 달리 커스텀 속성 클래스는 Attribute 클래스 자체를 확장해요.

이렇게 하면 커스텀 클래스 선언자를 쓰는 클래스 안에서 has 속성의 동작을 바꿀 수 있어요. 여전히 has로 속성을 선언하지만, 기본 Attribute 클래스 대신 커스텀 속성 클래스의 인스턴스로 인스턴스화돼요.

커스텀 속성 클래스 부분만 보여 주는 예제(ValueClass 기반)를 볼게요:

# Custom attribute class example
# Note: This is just the attribute class part. You would also need to define
# a HOW class for the class declarator (like MetamodelX::ValueClassHOW).

class ValueClass::Attribute is Attribute {
    method compose(|) {
        # Custom behavior during attribute composition
        # For example, validate that attributes can't be rw
        die "Attributes { $.name } can't be rw on a value-class" if $.rw;
        nextsame
    }

    method container_initializer(|c) {
        # Custom initialization logic
        # This can change default values or types
        if $.name.starts-with: '@' {
            return -> { Tuple.new }
        } elsif $.name.starts-with: '%' {
            return -> { ValueMap.new }
        }
        nextsame
    }
}

# Export the attribute class via EXPORTHOW::DECLARE
# (The class declarator would also be exported here)
my package EXPORTHOW {
    package DECLARE {
        # constant value-class = MetamodelX::ValueClassHOW;  # Not shown here
        constant value-class-attr = ValueClass::Attribute;
    }
}

이렇게 사용해요:

use ValueClass;

value-class MyClass {
    has $.name;    # Regular scalar attribute (with ValueClass validation)
    has @.items;   # This becomes a Tuple
    has %.data;    # This becomes a ValueMap
}

my $obj = MyClass.new(name => "Test", items => [1,2,3]);
say $obj.name;   # OUTPUT: «Test␤»

동작 방식:

  • Raku의 모든 속성의 기본 클래스인 Attribute를 확장한 클래스를 만들어요.
  • composecontainer_initializer 같은 메서드를 오버라이드해서 has로 만들어지는 속성의 동작을 커스터마이즈할 수 있어요.
  • EXPORTHOW::DECLARE 패키지로 내보내는데, 상수 이름(보통 -attr로 끝남)이 커스텀 속성 클래스를 가리켜요.
  • 커스텀 클래스 선언자(예: value-class)를 쓰면, 그 클래스 안에 선언된 모든 has 속성이 기본 Attribute 클래스 대신 커스텀 속성 클래스의 인스턴스로 인스턴스화돼요.
  • 여전히 has로 속성을 선언해요. 커스텀 속성 클래스는 그 속성들이 어떤 클래스로 인스턴스화되는지만 바꿀 뿐이에요.

실제 사례는 ValueClass가 소스 코드에서 커스텀 속성을 구현한 방식을 보면 돼요.

슬랭을 이해하기 위한 사전 지식

기존 슬랭을 사용하기만 하려면 여기서 읽기를 멈춰도 돼요. 하지만 슬랭을 만들거나 수정하는 방법을 더 이해하고 싶다면, 그 전에 다음 문서를 읽어 두는 걸 권장해요.

  • Grammar 튜토리얼
  • Grammars
  • (필수는 아니지만 도움이 되는) class Grammar

슬랭 예제

좋고 단순한 슬랭을 보고 싶다면 Slang::Lambda의 소스가 가장 좋은 후보예요.

Slang::Lambda는 Raku 그래머를 수정해서 λ (람다) 기호를 pointy block(보통 ->)의 시작 기호로 쓸 수 있게 해 줘요. 소스가 너무 짧아서 여기에 그대로 실을 수 있어요:

my role RakuLambda {
    token pointy-block-starter { '->' | '→' | '<->' | '↔' | 'λ' }
}

my role LegacyLambda {
    token lambda { '->' | '<->' | 'λ' }
}

use Slangify:ver<0.0.4+>:auth<zef:lizmat> RakuLambda, Mu, LegacyLambda;

그리고 사용하려면:

use Slang::Lambda;

say (1,2,3).map: λ $x { $x+1 }  # (2 3 4)

Raku의 그래머에 익숙한 사람이라면 코드가 자명해요. 다만 코드를 읽을 줄 아는 것과 줄 아는 것 사이에는 큰 간격이 있는 경우가 많아요. Slang::Lambda의 저자 Elizabeth는 어떻게 Slangify를 써야 하고 어떤 토큰 이름을 선언해야 하는지 알았을까요? 그녀의 경우엔 Raku를 깊이 연구했기 때문이에요. 우리는 그 과정의 일부를 지금부터 단계적으로 살펴볼게요.

Slangify

슬랭을 만드는 과정을 쉽게 하기 위해 Elizabeth Mattijsen은 Slangify 모듈도 만들었어요. Slangify는 슬랭 개발자를 위한 모듈이에요.

슬랭 생성과 활성화의 내부를 추상화해서, 슬랭 모듈 개발자들이 Raku 언어의 현재·미래 버전 전반에 걸쳐 일관되게 작업할 수 있는 인터페이스를 제공해요.

사용법은 이래요:

use Slangify Foo::Grammar, Foo::Actions;

Grammar나 Actions 중 하나를 Mu로 바꾸면 그 부분은 그대로 두라는 뜻이에요.

레거시 그래머와 레거시 액션을 위한 두 추가 파라미터도 받아들여져요.

Slangify 사용이 논리적으로는 코드의 마지막 단계지만, 코드를 맞추기 위해서라도 시작하기 전에 어느 정도 이해해 두는 게 좋아요. 자세한 내용은 Slangify 문서를 읽어 보세요.

Raku 그래머

여기서 가장 많이 파야 할 부분이에요. Raku 그래머는 작지도 않고 단순하지도 않아요. 프로그래밍할 때 훌륭한 방대한 오류 메시지들이 오히려 가독성을 해치기도 하죠.

Raku 그래머의 소스는 두 가지예요.

  • 새 AST 그래머
  • 옛(pre-AST) 그래머

안타깝게도 그래머를 뒤져서 오버라이드하려는 토큰/규칙을 찾는 것 외에 대안은 없어요.

슬랭 그래머

이제 Slang::Lambda처럼 슬랭 그래머를 작성해야 해요. 위에 링크한 그래머 문서가 도움이 될 거예요.

슬랭 액션

많은 슬랭은 그것을 뒷받침하는 Actions 클래스도 필요해요. 이것도 위에 링크한 그래머 문서에 설명되어 있어요.

모듈 배포

모듈도 배포하고 싶을 거예요. 그러려면 "Distributions: An Introduction"과 관련 페이지의 지침을 따르면 돼요.