3 điểm bởi GN⁺ 2024-05-03 | 1 bình luận | Chia sẻ qua WhatsApp

Giới thiệu ngôn ngữ lập trình Cognition

Vấn đề đặt ra

  • Các lập trình viên Lisp cho rằng có thể tạo metaprogramming và hệ thống tổng quát nhờ mã S-expression và hệ thống macro năng lực cao
  • Nhưng Lisp có những vấn đề cốt lõi:
    • metaprogramming và lập trình không giống nhau, vì vậy Lisp luôn có cú pháp chặt chẽ (một số ký tự cho ngoặc hoặc cho việc look-ahead)
    • dấu ngoặc trái cho Lisp biết phải tiếp tục đọc cho đến khi gặp dấu ngoặc phải
    • điều này khiến chữ trái/phải ngoặc trở thành không thể thay đổi trong ngôn ngữ (về mặt khái niệm không phải, nhưng ở một số hiện thực là không khả thi)
    • quan trọng hơn, không thể thay đổi trật tự phân tách token như vậy sau này nếu không xử lý chuỗi
  • Các ngôn ngữ khác cũng có cách khác để quyết định trước nội dung cần đọc tiếp bằng cách dựa vào token cụ thể
    • quá trình này được gọi là cú pháp (syntax)
  • Cognition sử dụng antisyntax với cú pháp hậu tố (postfix) hoàn toàn nên khác biệt
    • điều này tương tự như ngôn ngữ concatenative, nhưng ngôn ngữ concatenative cũng có hai vấn đề chính:
      1. việc đưa vào ký tự ngoặc trái/phải (thực ra là cú pháp tiền tố)
      2. ký tự trích dẫn chuỗi
    • nó không phù hợp cho ngôn ngữ phổ biến
    • các vấn đề tương tự cũng xuất hiện trong các hiện thực cú pháp C của Lisp (quá nhiều ký tự escape, cần ký tự khoảng trắng để phân biệt bắt đầu/kết thúc token cụ thể)
  • Racket có hệ thống macro nhưng không dynamic ở runtime và dùng tiền xử lý

Giới thiệu Cognition

  • Dự án đã được nghiên cứu cùng với Matthew Hinton trong vài tháng
  • Mục tiêu là xây dựng một trong những hệ thống cú pháp tổng quát nhất bằng việc sử dụng cú pháp hậu tố hoàn toàn
  • Có thể cần kiến thức nền về cú pháp/tokenization/parsing, nhưng cố gắng giải thích theo cách dễ hiểu nhất
  • Kho lưu trữ: https://github.com/metacrank/cognition

Baremetal Cognition

  • Baremetal Cognition tương tự Brainfuck nhưng cho phép metaprogramming sâu
  • Ví dụ mã bootstrap:
ldfgldftgldfdtgldf dfiff1 crank f
  • Khoảng trắng và xuống dòng rất quan trọng
    • Dòng thứ hai có khoảng trắng sau df
    • Dòng thứ ba có ký tự khoảng trắng
  • Nhờ đó có thể đưa vào hai khái niệm mới là delimiter và ignore

Tokenization

  • Delimiter cho phép tokenizer phân biệt điểm bắt đầu và kết thúc token
  • Danh sách tokenizer ký tự đơn được công khai để sửa đổi trong Cognition
  • Ký tự ignored là ký tự mà tokenizer bỏ qua hoàn toàn ở bước đầu tiên của mọi vòng read-eval-print
    • tức là khi bắt đầu thu thập token sẽ bỏ qua các ký tự trong tập ignored
  • Mặc định tất cả ký tự là delimiter và không có ký tự nào bị ignored
  • Với các danh sách delimiter và ignored, có thể bật/tắt blacklist/whitelist ký tự để mang lại tính ngắn gọn và thực dụng

Falias

  • Falias là danh sách từ được thực thi khi được đặt lên stack
  • Mọi Falias đều thực thi phần tử trên cùng stack (tương đương với eval trong Stem)
  • f là Falias mặc định, nó thực thi d trên đỉnh stack mà không cần đẩy f lên stack
  • d biến danh sách delimiter thành chuỗi giá trị của từ (tức là loại bỏ ký tự l khỏi delimiter)
  • Trong môi trường mặc định, không có từ nào được thực thi ngoài các Falias đặc biệt

Lưu ý về delimiter

  • Delimiter có một quy tắc thú vị
    • trong vòng tokenization, nếu không bỏ qua ký tự, ký tự delimiter sẽ được đưa vào token hiện tại rồi tiếp tục
    • điều này trái ngược với singlet (đưa chính nó vào token rồi skip để kết thúc thu thập token)
  • Có thể đặt blacklist/whitelist
    • có thể blacklist/whitelist các danh sách delimiter, singlet, ignored character
    • mặc định không có delimiter nào bị blacklist, singlet nào bị whitelist, hoặc ignored nào bị whitelist
  • Tất cả các ký tự còn lại được thu thập vào token hiện tại và tiếp tục thu thập ký tự mới cho tới khi vòng lặp dừng bởi quy tắc delimiter hoặc singlet

Tiếp tục mã bootstrap

ldf
  • l trở thành ký tự không phải delimiter
gldftgldfdtgldf  dfiff1 crank f
  • d là delimiter nên gl được đẩy lên stack, Falias f được gọi để biến gl thành ký tự không phải delimiter
  • tgl được đẩy lên stack và trở thành không phải delimiter bởi df
  • dtgl được đẩy lên stack và bởi \ndf, newline (\n) trở thành duy nhất ký tự không phải delimiter (newline có trong mã thực tế)
  • Theo quy tắc delimiter, ký tự space và \n được đẩy lên stack (dòng thứ 3 chứa space)
  • Một từ \ \n khác được token hóa
  • Stack hiện tại như sau (từ dưới lên trên): 3. dtgl 2. [kí tự space]\n
    1. [kí tự space]\n
  • df đặt \ \n thành ký tự không phải delimiter
  • if đặt \ \n thành ký tự ignored (bỏ qua khi bắt đầu tokenization)
  • f thực thi dtgl để đẩy toggle của dflag lưu lại việc whitelist/blacklist delimiter
  • Bây giờ mọi ký tự không phải delimiter trở thành delimiter và mọi delimiter trở thành không phải delimiter
  • Cuối cùng, space và newline trở thành delimiter của token và bị ignored khi bắt đầu token
  • Tiếp đó 1 được token hóa và đẩy lên stack, rồi từ crank token hóa rồi được f thực thi (1 trong trường hợp này được coi là số nhưng trong Cognition tất cả đều là từ)
  • Hoàn thành chuỗi bootstrap! crank làm gì sẽ được giải thích ở phần sau

Tóm tắt bootstrap

  • Cognition cho phép thay đổi phương thức tokenization theo lập trình một cách động
    • không thể làm điều này trong các ngôn ngữ khác
    • bạn có thể lập trình tokenizer cho ngôn ngữ ngoài ngay trong Cognition và token hóa theo ý muốn
  • Nhờ dùng cú pháp hậu tố và không look-ahead
    • không cần parse hơn một token trước khi đánh giá biểu thức
  • Dùng Falias, các từ có thể được thực thi mà không cần từ tiền tố hay hàm thực thi mặc định

Crank

  • Hệ thống metacrank cho phép cấu hình cách mặc định để thực thi token trên stack
  • Từ crank nhận một đối số số và mỗi khi đưa n từ vào stack sẽ thực thi phần tử trên cùng
  • Ví dụ mã (crank 1 đã được đặt):
5 crank 2
crank 2 crank 
1 crank unglue swap quote prepose def
  • Trong môi trường crank 1, có thể tắt việc dùng f trong lúc đánh giá token
    • mỗi token theo cú pháp phân tách bởi newline và space sẽ được đánh giá từng token một
  • Mã bắt đầu thử đánh giá 5 (không phải builtin nên tự đánh giá)
  • crank được cấu hình để thực thi khi có 5 token trong stack (crank 5 được đặt nên crank không thực thi)
  • 2crank, 2, crank, 1 đều được đẩy lên stack (crank không được thực thi bởi vì đang được crank 5 thiết lập): 4. 2crank 3. 2 2. crank
    1. 1
  • crank là từ thứ 5 nên được thực thi (crank 1 đang đặt)
  • unglue là builtin lấy giá trị của từ trên cùng stack (được sử dụng bởi 1)
    • tức là lấy function pointer gắn với builtin crank
  • Stack hiện tại: 3. 2crank 2. 2
    1. [CLIB]
    • CLIB là function pointer trỏ tới builtin crank
  • Thực thi swap: 3. 2crank 2. [CLIB]
    1. 2
  • Thực thi quote (builtin trích dẫn phần tử trên cùng stack): 3. 2crank 2. [CLIB]
    1. [2]
  • Thực thi prepose (giống compose trong Stem nhưng đặt trước và cho là VMACRO): 2. 2crank
    1. ([2] [CLIB])
  • Gọi def
    • Định nghĩa từ 2crank để đẩy 2 rồi gọi function pointer của builtin crank
  • Cần giải thích VMACRO là gì, và sự khác biệt giữa stack Cognition và Stem stack

Khác biệt với Stem

  • Trong stack Stem có thể đặt từ trực tiếp
  • Trong Cognition, từ được đưa vào container trước rồi mới đẩy vào stack thay vì được evaluate ngay
    • Ở Stem, từ như compose hoạt động giữa một từ (hoặc container có một từ) và container khác
    • Điều này làm cho API của Cognition nhất quán hơn
  • Những từ như cd cũng tận dụng khái niệm này

Macro

  • Một điểm khác nữa giữa quoting của Stem và container của Cognition
  • Khi đánh giá macro, mọi thứ bên trong macro đều được đánh giá và crank bị ignore
  • Khi một từ được binding, đánh giá từ đó sẽ

1 bình luận

 
GN⁺ 2024-05-03
Ý kiến từ Hacker News

Một số ý kiến chính có thể tóm tắt như sau:

  • Trong phần mở đầu của tài liệu, việc giải thích về chính dự án Cognition xuất hiện quá muộn. Nên đặt nội dung quan trọng nhất lên trước để tiết kiệm thời gian của người đọc.
  • Giống như tính năng cấu hình lớp reader của Racket, đã có những cách tiếp cận khác nhằm mở rộng cú pháp trong khi vẫn duy trì khả năng tương thích. Có một sự nghi ngờ rằng cách tiếp cận của Cognition có thực sự "tốt hơn" về bản chất hay không.
  • Common Lisp cũng có thể thay đổi cú pháp một cách tự do bằng reader macro, macro, compiler macro, v.v. Cốt lõi của siêu lập trình là xử lý ngữ nghĩa hơn là cú pháp.
  • Khả năng của Cognition trong việc định nghĩa, định nghĩa lại cấu trúc cú pháp lúc runtime và đi vào/thoát ra khỏi chúng là đẹp mắt và thú vị. Nó mở ra khả năng xây dựng một cỗ máy "tư duy" thực sự.
  • Vì cú pháp cung cấp cấu trúc, nên việc loại bỏ chính cú pháp là một mâu thuẫn. Cú pháp quá gọn gàng có thể làm giảm tính dễ đọc và dễ hiểu.
  • Cách viết của chính tài liệu có phần quá dài dòng và mang màu sắc châm biếm, khiến cho việc đọc khó khăn. Nhưng nó đang bàn về những nội dung rất sâu sắc.