GN⁺: Cognition: 메타프로그래밍을 재정의하는 새로운 antisyntax 언어
(ret2pop.nullring.xyz)코그니션 프로그래밍 언어 소개
문제 제기
- Lisp 프로그래머들은 S-표현식 코드와 기능적인 매크로 시스템으로 메타프로그래밍과 일반화된 시스템을 만들 수 있다고 주장함
- 하지만 Lisp에는 근본적인 문제가 있음
- 메타프로그래밍과 프로그래밍이 동일하지 않아서 Lisp에는 항상 엄격한 문법이 존재함 (괄호나 look-ahead를 위한 특정 문자들)
- 왼쪽 괄호는 Lisp에게 오른쪽 괄호를 만날 때까지 계속 읽어야 함을 알려줌
- 이는 왼쪽/오른쪽 괄호가 언어 내에서 변경 불가능하게 만듦 (개념적으로는 아니지만 일부 구현에서는 불가능)
- 더 중요한 것은, 이러한 토큰들이 구분되는 순서를 사후에 변경하는 것이 문자열 처리 없이는 불가능함
- 다른 언어들도 특정 토큰을 보고 앞으로 읽어야 할 내용을 결정하는 다른 방식을 가지고 있음
- 이 과정을 문법(syntax)이라고 함
- Cognition은 완전한 후위 표기법(postfix)을 사용하는 반문법(antisyntax)을 사용하여 다름
- 이는 연결형 프로그래밍 언어와 유사하지만, 연결형 프로그래밍 언어도 두 가지 주요 문제가 있음
- 왼쪽/오른쪽 대괄호 문자 도입 (사실 전위 표기법)
- 문자열용 따옴표 문자
- 이는 일반적인 언어로는 부적합함
- Lisp의 C 문법 구현에서도 동일한 문제 발견 (이스케이프 문자 남발, 특정 토큰 시작/끝 구분을 위한 공백 문자 필요)
- 이는 연결형 프로그래밍 언어와 유사하지만, 연결형 프로그래밍 언어도 두 가지 주요 문제가 있음
- Racket은 매크로 시스템을 가지고 있지만 런타임에 동적이지 않고 전처리를 활용함
Cognition 소개
- Matthew Hinton과 함께 몇 달 동안 연구해온 프로젝트
- 완전한 후위 표기법을 사용하여 알려진 가장 일반화된 문법 시스템 중 하나를 만드는 것이 목표
- 문법/토큰화/구문 분석에 대한 배경 지식이 필요할 수 있지만, 이해하기 쉽게 설명하려고 노력함
- 저장소: https://github.com/metacrank/cognition
Baremetal Cognition
- Baremetal Cognition은 Brainfuck과 유사하지만 심각한 메타프로그래밍이 가능함
- 부트스트랩 코드 예제:
ldfgldftgldfdtgldf dfiff1 crank f
- 공백과 줄바꿈이 중요함
- 2번째 줄은
df
뒤에 공백 - 3번째 줄에는 공백 문자
- 2번째 줄은
- 이를 통해 구분자(delimiter)와 무시(ignore)라는 두 가지 새로운 개념 도입 가능
Tokenization
- 구분자(Delimiter)는 토크나이저가 토큰의 시작과 끝을 구분하도록 해줌
- 단일 문자 토크나이저 목록은 공개되어 있어서 Cognition 내부에서 수정하고 읽을 수 있음
- 무시되는 문자(Ignored character)는 모든 read-eval-print 루프의 첫 번째 단계에서 토크나이저에 의해 완전히 무시됨
- 즉, 토큰 수집 시작 시 설정된 무시 문자 집합을 건너뜀
- 기본적으로 모든 문자는 구분자이고 무시되는 문자는 없음
- 구분자와 무시 문자 목록으로 주어진 문자에 대해 블랙리스트/화이트리스트 토글이 가능 (간결성과 실용성 제공)
Falias
- Falias는 스택에 놓일 때 실행되는 단어 목록
- 모든 Falias는 스택 최상단을 실행 (Stem에서의
eval
과 동등) -
f
는 기본 Falias로, 스택에 놓이지 않고 스택 최상단인d
를 실행함 -
d
는 구분자 목록을 단어의 문자열 값으로 변경 (즉,l
문자만 구분자에서 제외) - 기본 환경에서는 특수 Falias를 제외하고는 어떤 단어도 실행되지 않음
구분자 주의사항
- 구분자에는 흥미로운 규칙이 있음
- 토큰화 루프에서 문자를 무시하지 않은 경우, 구분자 문자는 현재 토큰의 일부로 포함되고 계속 진행됨
- 이는 싱글릿(singlet)과 대조적임 (토큰에 자신을 포함시키고 건너뛰어서 토큰 수집 종료)
- 블랙리스트 여부도 설정 가능
- 구분자, 싱글릿, 무시 문자 목록을 블랙리스트/화이트리스트 할 수 있음
- 기본적으로 블랙리스트된 구분자, 화이트리스트된 싱글릿, 화이트리스트된 무시 문자는 없음
- 다른 모든 문자는 현재 토큰의 일부로 수집되며, 구분자나 싱글릿 규칙에 의해 루프가 중단될 때까지 새로운 문자 계속 수집
부트스트랩 코드 계속
ldf
-
l
을 구분자가 아닌 문자로 만듦
gldftgldfdtgldf dfiff1 crank f
-
d
가 구분자이기 때문에gl
이 스택에 놓이고, Faliasf
가 호출되어gl
이 구분자가 아닌 문자가 됨 -
tgl
이 스택에 놓이고df
에 의해 구분자가 아닌 문자가 됨 -
dtgl
이 스택에 놓이고\ndf
에 의해 개행(\n)이 유일한 구분자가 아닌 문자가 됨 (개행이 실제 코드에 포함) - 구분자 규칙에 의해 공백 문자와
\n
이 스택에 놓임 (3번째 줄에 공백 포함) - 또 다른
\ \n
단어가 토큰화됨 - 현재 스택은 다음과 같음 (아래서 위로):
3. dtgl
2. [공백 문자]\n
- [공백 문자]\n
-
df
는\ \n
을 구분자가 아닌 문자로 설정 -
if
는\ \n
을 무시 문자로 설정 (토큰화 시작 시 무시) -
f
는dtgl
을 실행하여 구분자의 화이트리스트/블랙리스트 구분을 저장하는dflag
토글 - 이제 모든 구분자가 아닌 문자가 구분자가 되고, 모든 구분자가 구분자가 아닌 문자가 됨
- 마지막으로 공백과 개행이 토큰의 구분자가 되고, 토큰 시작 시 무시됨
- 그 다음
1
이 토큰화되어 스택에 놓이고,crank
단어가 토큰화된 후f
에 의해 실행됨 (1
토큰은 이 경우 숫자로 취급되지만 Cognition에서는 모든 것이 단어임) - 부트스트래핑 시퀀스 완료!
crank
가 무엇을 하는지는 다음 섹션에서 설명
부트스트래핑 요약
- Cognition은 토큰화 방식을 프로그래밍 방식으로 동적 변경 가능
- 다른 언어에서는 불가능한 일
- 외부 언어를 위한 토크나이저를 Cognition 내에서 프로그래밍하고 원하는 대로 토큰화 가능
- 후위 표기법을 사용하고 look-ahead하지 않기 때문에 가능
- 표현식을 평가하기 전에 하나 이상의 토큰을 구문 분석할 필요 없음
- Falias를 사용하여 접두사 단어나 기본 단어 실행 없이도 단어 실행 가능
Crank
- metacrank 시스템은 스택에 토큰을 실행하는 기본 방법 설정 가능
-
crank
단어는 숫자를 인자로 받아 스택에 n개의 단어를 넣을 때마다 스택 최상단을 실행 - 예시 코드 (
crank 1
로 설정된 상태):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
-
crank 1
환경에서는 토큰 평가 시f
사용 중단 가능- 토큰화된 토큰 1개마다 1개가 평가됨
- 개행과 공백으로 구분된 문법을 프로그래밍했기 때문에 코드를 직관적으로 해석 가능
- 코드는
5
평가를 시도하며 시작 (빌트인이 아니므로 자기 자신으로 평가) -
crank
는 스택에 5개의 토큰이 놓일 때마다 실행되도록 평가되어 설정 -
2crank
,2
,crank
,1
은 모두 스택에 놓임 (crank 5
로 설정되어 있어서crank
는 빌트인이지만 실행되지 않음): 4. 2crank 3. 2 2. crank- 1
-
crank
는 5번째 단어이므로 실행됨 (crank 1
로 설정) -
unglue
는 빌트인으로 스택 최상단 단어(1
에 의해 사용)의 값을 가져옴- 즉,
crank
빌트인과 연결된 함수 포인터를 가져옴
- 즉,
- 스택은 다음과 같음:
3. 2crank
2. 2
- [CLIB]
- CLIB는
crank
빌트인을 가리키는 함수 포인터
-
swap
실행: 3. 2crank 2. [CLIB]- 2
-
quote
(스택 최상단을 인용하는 빌트인) 실행: 3. 2crank 2. [CLIB]- [2]
-
prepose
(stem
의compose
와 유사하지만 앞에 놓고 VMACRO라고 부르는 것에 놓음) 실행: 2. 2crank- ([2] [CLIB])
-
def
호출-
2
를 스택에 넣고crank
빌트인을 가리키는 함수 포인터를 호출하는2crank
단어 정의
-
- VMACRO가 무엇인지, Cognition 스택과 Stem 스택의 차이점 설명 필요
Stem과의 차이점
- Stem 스택에서는 단어를 직접 스택에 넣을 수 있음
- Cognition에서는 단어가 평가되지 않고 컨테이너에 넣어져서 스택에 놓임
- Stem에서
compose
같은 단어가 단어(또는 단일 단어가 있는 컨테이너)와 다른 컨테이너에서 작동 - 이는 Cognition의 API를 더 일관성 있게 만듦
- Stem에서
-
cd
같은 단어도 이 개념 활용
매크로
- Stem 인용과 Cognition 컨테이너의 또 다른 차이점
- 매크로 평가 시 매크로 내부의 모든 것이 평가되고 crank는 무시됨
- 단어에 바인딩되면, 해당 단어 평가 시
Hacker News 의견
몇 가지 주요 의견들을 요약하면:
- 문서의 서론 부분에서 Cognition 프로젝트 자체에 대한 설명이 너무 늦게 나옴. 독자의 시간을 아끼기 위해 가장 중요한 내용을 먼저 제시하는 것이 좋음.
- Racket의 reader 레이어 설정 기능처럼, 문법을 확장하면서도 상호운용성을 유지하는 다른 접근 방식들이 이미 존재함. Cognition의 접근법이 근본적으로 "더 나은지"에 대해서는 의문이 있음.
- Common Lisp도 reader macro, macro, compiler macro 등으로 문법을 자유롭게 바꿀 수 있음. 메타프로그래밍은 문법보다는 의미론을 다루는 것이 핵심임.
- Cognition이 런타임에 문법 구조를 정의하고 재정의하며 들어갔다 나올 수 있는 능력은 아름답고 흥미로움. 진정한 "사고하는" 기계를 만들 수 있는 가능성을 열어줌.
- 문법은 구조를 제공하는 것이므로 문법 자체를 없앤다는 것은 모순임. 지나치게 간결한 문법은 오히려 가독성과 이해성을 해칠 수 있음.
- 문서 자체의 서술 방식이 다소 장황하고 풍자적인 느낌이 있어서 읽기 어려움. 하지만 깊이 있는 내용을 다루고 있음.