3P by neo 1달전 | favorite | 댓글 1개

코그니션 프로그래밍 언어 소개

문제 제기

  • Lisp 프로그래머들은 S-표현식 코드와 기능적인 매크로 시스템으로 메타프로그래밍과 일반화된 시스템을 만들 수 있다고 주장함
  • 하지만 Lisp에는 근본적인 문제가 있음
    • 메타프로그래밍과 프로그래밍이 동일하지 않아서 Lisp에는 항상 엄격한 문법이 존재함 (괄호나 look-ahead를 위한 특정 문자들)
    • 왼쪽 괄호는 Lisp에게 오른쪽 괄호를 만날 때까지 계속 읽어야 함을 알려줌
    • 이는 왼쪽/오른쪽 괄호가 언어 내에서 변경 불가능하게 만듦 (개념적으로는 아니지만 일부 구현에서는 불가능)
    • 더 중요한 것은, 이러한 토큰들이 구분되는 순서를 사후에 변경하는 것이 문자열 처리 없이는 불가능함
  • 다른 언어들도 특정 토큰을 보고 앞으로 읽어야 할 내용을 결정하는 다른 방식을 가지고 있음
    • 이 과정을 문법(syntax)이라고 함
  • Cognition은 완전한 후위 표기법(postfix)을 사용하는 반문법(antisyntax)을 사용하여 다름
    • 이는 연결형 프로그래밍 언어와 유사하지만, 연결형 프로그래밍 언어도 두 가지 주요 문제가 있음
      1. 왼쪽/오른쪽 대괄호 문자 도입 (사실 전위 표기법)
      2. 문자열용 따옴표 문자
    • 이는 일반적인 언어로는 부적합함
    • Lisp의 C 문법 구현에서도 동일한 문제 발견 (이스케이프 문자 남발, 특정 토큰 시작/끝 구분을 위한 공백 문자 필요)
  • Racket은 매크로 시스템을 가지고 있지만 런타임에 동적이지 않고 전처리를 활용함

Cognition 소개

  • Matthew Hinton과 함께 몇 달 동안 연구해온 프로젝트
  • 완전한 후위 표기법을 사용하여 알려진 가장 일반화된 문법 시스템 중 하나를 만드는 것이 목표
  • 문법/토큰화/구문 분석에 대한 배경 지식이 필요할 수 있지만, 이해하기 쉽게 설명하려고 노력함
  • 저장소: https://github.com/metacrank/cognition

Baremetal Cognition

  • Baremetal Cognition은 Brainfuck과 유사하지만 심각한 메타프로그래밍이 가능함
  • 부트스트랩 코드 예제:
ldfgldftgldfdtgldf dfiff1 crank f
  • 공백과 줄바꿈이 중요함
    • 2번째 줄은 df 뒤에 공백
    • 3번째 줄에는 공백 문자
  • 이를 통해 구분자(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이 스택에 놓이고, Falias f가 호출되어 gl이 구분자가 아닌 문자가 됨
  • tgl이 스택에 놓이고 df에 의해 구분자가 아닌 문자가 됨
  • dtgl이 스택에 놓이고 \ndf에 의해 개행(\n)이 유일한 구분자가 아닌 문자가 됨 (개행이 실제 코드에 포함)
  • 구분자 규칙에 의해 공백 문자와 \n이 스택에 놓임 (3번째 줄에 공백 포함)
  • 또 다른 \ \n 단어가 토큰화됨
  • 현재 스택은 다음과 같음 (아래서 위로): 3. dtgl 2. [공백 문자]\n
    1. [공백 문자]\n
  • df\ \n을 구분자가 아닌 문자로 설정
  • if\ \n을 무시 문자로 설정 (토큰화 시작 시 무시)
  • fdtgl을 실행하여 구분자의 화이트리스트/블랙리스트 구분을 저장하는 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. 1
  • crank는 5번째 단어이므로 실행됨 (crank 1로 설정)
  • unglue는 빌트인으로 스택 최상단 단어(1에 의해 사용)의 값을 가져옴
    • 즉, crank 빌트인과 연결된 함수 포인터를 가져옴
  • 스택은 다음과 같음: 3. 2crank 2. 2
    1. [CLIB]
    • CLIB는 crank 빌트인을 가리키는 함수 포인터
  • swap 실행: 3. 2crank 2. [CLIB]
    1. 2
  • quote (스택 최상단을 인용하는 빌트인) 실행: 3. 2crank 2. [CLIB]
    1. [2]
  • prepose (stemcompose와 유사하지만 앞에 놓고 VMACRO라고 부르는 것에 놓음) 실행: 2. 2crank
    1. ([2] [CLIB])
  • def 호출
    • 2를 스택에 넣고 crank 빌트인을 가리키는 함수 포인터를 호출하는 2crank 단어 정의
  • VMACRO가 무엇인지, Cognition 스택과 Stem 스택의 차이점 설명 필요

Stem과의 차이점

  • Stem 스택에서는 단어를 직접 스택에 넣을 수 있음
  • Cognition에서는 단어가 평가되지 않고 컨테이너에 넣어져서 스택에 놓임
    • Stem에서 compose 같은 단어가 단어(또는 단일 단어가 있는 컨테이너)와 다른 컨테이너에서 작동
    • 이는 Cognition의 API를 더 일관성 있게 만듦
  • cd 같은 단어도 이 개념 활용

매크로

  • Stem 인용과 Cognition 컨테이너의 또 다른 차이점
  • 매크로 평가 시 매크로 내부의 모든 것이 평가되고 crank는 무시됨
  • 단어에 바인딩되면, 해당 단어 평가 시
Hacker News 의견

몇 가지 주요 의견들을 요약하면:

  • 문서의 서론 부분에서 Cognition 프로젝트 자체에 대한 설명이 너무 늦게 나옴. 독자의 시간을 아끼기 위해 가장 중요한 내용을 먼저 제시하는 것이 좋음.
  • Racket의 reader 레이어 설정 기능처럼, 문법을 확장하면서도 상호운용성을 유지하는 다른 접근 방식들이 이미 존재함. Cognition의 접근법이 근본적으로 "더 나은지"에 대해서는 의문이 있음.
  • Common Lisp도 reader macro, macro, compiler macro 등으로 문법을 자유롭게 바꿀 수 있음. 메타프로그래밍은 문법보다는 의미론을 다루는 것이 핵심임.
  • Cognition이 런타임에 문법 구조를 정의하고 재정의하며 들어갔다 나올 수 있는 능력은 아름답고 흥미로움. 진정한 "사고하는" 기계를 만들 수 있는 가능성을 열어줌.
  • 문법은 구조를 제공하는 것이므로 문법 자체를 없앤다는 것은 모순임. 지나치게 간결한 문법은 오히려 가독성과 이해성을 해칠 수 있음.
  • 문서 자체의 서술 방식이 다소 장황하고 풍자적인 느낌이 있어서 읽기 어려움. 하지만 깊이 있는 내용을 다루고 있음.