Piet: Ngôn ngữ lập trình tạo ra các chương trình trông như tranh trừu tượng (phát triển năm 2002)
(dangermouse.net)- Piet là một ngôn ngữ lập trình bí truyền được tạo ra để mã trông như tranh trừu tượng, được đặt theo tên Piet Mondrian, người tiên phong của nghệ thuật trừu tượng hình học
- Chương trình là đồ họa được tạo từ 20 màu được nhận diện; trình thực thi di chuyển giữa các khối màu và diễn giải mức thay đổi màu thành lệnh
- Mọi dữ liệu chỉ tồn tại dưới dạng số nguyên và được lưu trong stack; kích thước của khối màu là giá trị, nhưng không tự động được đưa lên stack nếu không có lệnh
push - Điều khiển luồng được quyết định bởi Direction Pointer và Codel Chooser, cùng các quy tắc về khối đen, cạnh và khối trắng; một số hành vi có thể khác nhau tùy triển khai
- Có ví dụ và hệ sinh thái công cụ bên ngoài, nhưng không có trình thông dịch chính thức có thẩm quyền; xử lý lỗi và cách diễn giải màu không chuẩn vẫn phụ thuộc vào triển khai
Ý tưởng cơ bản của Piet
- Piet là một ngôn ngữ lập trình mà mã chương trình trông như nghệ thuật trừu tượng
- Tên gọi được lấy từ Piet Mondrian, người tiên phong của nghệ thuật trừu tượng hình học
- Ban đầu muốn dùng tên
Mondrian, nhưng vì đã có một ngôn ngữ kịch bản cùng tên nên đã dùng tên Piet - Sau khi đặc tả được viết, một cộng đồng nhỏ đã hình thành và tạo ra các chương trình, trình thông dịch, IDE và trình biên dịch
- Không có trình thông dịch chính thức có thẩm quyền; các triển khai hiện có có thể diễn giải đặc tả hơi khác nhau
- Một số phần giải thích đặc tả đã được bổ sung, nhưng có khả năng một số triển khai hiện tại không tuân theo chúng
Màu sắc và đơn vị mã
- Piet dùng tổng cộng 20 màu
- 18 màu thuộc vòng tuần hoàn màu và vòng tuần hoàn độ sáng
- Màu trắng và màu đen không thuộc hai vòng tuần hoàn này
- Vòng tuần hoàn màu có thứ tự
red -> yellow -> green -> cyan -> blue -> magenta -> red - Vòng tuần hoàn độ sáng có thứ tự
light -> normal -> dark -> lightlightcũng được xem là tối hơndarkmột bước, và điều ngược lại cũng đúng
- Có thể dùng các màu không chuẩn như cam hoặc nâu, nhưng hiệu ứng phụ thuộc vào triển khai
- Trong trường hợp đơn giản nhất, màu không chuẩn được xử lý như màu trắng
- Một khả năng khác là chúng có thể được xử lý như màu đen
Codel và khối màu
- Mã Piet là một hình ảnh đồ họa gồm các màu có thể nhận diện
- Vì từng pixel mã riêng lẻ có ý nghĩa về mặt ngôn ngữ, trong các chương trình được phóng to để dễ nhìn, một pixel đơn trong mã được gọi là codel
- Đơn vị thực thi cơ bản là khối màu
- Khối màu là vùng gồm các codel cùng màu nối với nhau theo trên-dưới-trái-phải
- Các khối chỉ chạm nhau theo đường chéo không được xem là nối liền
- Khối màu có thể có bất kỳ hình dạng nào và có thể chứa các lỗ màu khác bên trong
- Các lỗ bên trong không phải là một phần của khối đó
Stack và biểu diễn giá trị
- Piet lưu mọi giá trị dữ liệu trong stack
- Giá trị dữ liệu chỉ có số nguyên
- Tùy lệnh, có thể nhập hoặc xuất dưới dạng giá trị ký tự Unicode
- Về mặt khái niệm, stack có độ sâu vô hạn, nhưng triển khai có thể đặt kích thước stack tối đa hữu hạn
- Nếu xảy ra tràn trên stack hữu hạn thì đó là lỗi runtime, và cách xử lý phụ thuộc vào triển khai
- Các khối màu không phải đen và trắng biểu thị một giá trị số nguyên bằng số lượng codel trong khối đó
- Không thể biểu diễn trực tiếp số nguyên không dương
- Tuy nhiên có thể tạo ra chúng thông qua toán tử
- Giá trị của khối màu không tự động được push lên stack; cần lệnh
pushtường minh
- Về mặt khái niệm, kích thước số nguyên cũng là vô hạn, nhưng triển khai có thể đặt kích thước số nguyên tối đa hữu hạn
- Tràn số nguyên là lỗi runtime và cách xử lý phụ thuộc vào triển khai
Luồng thực thi
- Trình thông dịch bắt đầu thực thi tại khối màu chứa codel ở góc trên bên trái của chương trình
- Trong khi thực thi, hai trạng thái được duy trì
- Direction Pointer(DP): ban đầu chỉ sang phải, và có thể chỉ một trong các hướng phải, trái, xuống, lên
- Codel Chooser(CC): ban đầu chỉ sang trái, và có thể chỉ một trong hai phía trái hoặc phải
- Đích di chuyển tiếp theo được quyết định bởi biên của khối màu hiện tại và tổ hợp DP·CC
- Tìm cạnh của khối màu hiện tại xa nhất theo hướng DP
- Trên cạnh đó, chọn codel xa nhất theo hướng CC, tính theo hướng tiến của DP làm chuẩn
- Từ codel đó, di chuyển sang khối màu chứa codel ngay kế tiếp theo hướng DP
- Lặp lại quá trình này cho đến khi đạt điều kiện kết thúc thì chương trình kết thúc
Khối đen, cạnh và khối trắng
- Khối màu đen và cạnh của chương trình hoạt động như các rào chắn chặn luồng thực thi
- Nếu trình thông dịch cố di chuyển vào khối đen hoặc ra ngoài cạnh, nó dừng lại và chuyển đổi CC
- Nếu lần thử thứ hai cũng thất bại, DP được xoay một bước theo chiều kim đồng hồ
- Sau khi luân phiên đổi CC và DP tổng cộng 8 lần mà vẫn không thể rời khỏi khối màu hiện tại, chương trình kết thúc
-
Di chuyển qua khối trắng
- Khối màu trắng là vùng tự do mà trình thông dịch đi qua không bị cản trở
- Khi di chuyển từ một khối màu vào vùng trắng, trình thông dịch đi thẳng theo hướng DP cho đến khi chạm tới một khối màu không trắng
- Khi đi qua khối trắng để sang màu mới, không có lệnh nào được thực thi
- Nhờ đặc tính này, khối trắng hữu ích cho việc viết vòng lặp vì có thể thay đổi màu hiện tại mà không thực thi lệnh
- Di chuyển qua khối trắng không dùng quy trình chọn lối ra từ khối màu không trắng, mà chỉ thực hiện di chuyển thẳng
-
Khi bị chặn trong khối trắng
- Nếu đang đi thẳng qua khối trắng mà gặp khối đen hoặc cạnh, điều đó được xử lý như gặp hạn chế
- Trong trường hợp này, CC được chuyển đổi nhưng vị trí muốn di chuyển không thay đổi, nên DP lập tức được xoay một bước theo chiều kim đồng hồ
- Sau đó, từ codel trắng hiện tại, lại tiếp tục đi thẳng theo hướng DP mới
- Mỗi khi gặp hạn chế bên trong khối trắng, việc chuyển đổi CC và xoay DP được lặp lại
- Khi đi vào một khối màu thì thực thi tiếp tục; nếu bắt đầu lần theo đường cũ trong khối trắng thì không còn lối ra và thực thi kết thúc
Hệ thống lệnh
- Lệnh của Piet được quyết định bởi thay đổi màu khi di chuyển từ một khối màu sang khối màu tiếp theo
- Số bước đã di chuyển trong vòng tuần hoàn màu và số bước đã di chuyển trong vòng tuần hoàn độ sáng quyết định lệnh
- Không có lệnh nào được thực thi trong chuyển đổi màu đi qua khối trắng
-
Lệnh stack và số học
push: đưa giá trị của khối màu vừa rời khỏi lên stackpop: lấy và bỏ giá trị trên đỉnh stackadd: cộng hai giá trị trên cùng và đưa kết quả trở lại stacksubtract: đưa lên stack kết quả của giá trị thứ hai trừ đi giá trị trên đỉnhmultiply: nhân hai giá trị trên cùngdivide: chia nguyên giá trị thứ hai cho giá trị trên đỉnh- Chia cho 0 là lỗi phụ thuộc triển khai, và cách được khuyến nghị là bỏ qua lệnh
mod: đưa lên stack phần dư khi giá trị thứ hai được chia cho giá trị trên đỉnh- Kết quả có cùng dấu với số chia, tức giá trị trên đỉnh stack
- Nếu giá trị trên đỉnh là 0 thì đó là lỗi chia cho 0, và cách được khuyến nghị là bỏ qua lệnh
modvới số bị chia âm giống với phép chia lấy sàn được mô tả trong modulus operation trên Wikipedia
-
Lệnh so sánh, con trỏ và nhập/xuất
not: nếu giá trị trên đỉnh stack khác 0 thì đổi thành 0, nếu là 0 thì đổi thành 1greater: nếu giá trị thứ hai lớn hơn giá trị trên đỉnh thì đưa 1 lên stack, nếu không thì 0pointer: lấy giá trị trên đỉnh stack ra và xoay DP theo chiều kim đồng hồ số lần tương ứng- Nếu là số âm thì xoay ngược chiều kim đồng hồ
switch: lấy giá trị trên đỉnh stack ra và chuyển đổi CC số lần tương ứng- Nếu là số âm thì chuyển đổi theo trị tuyệt đối
duplicate: đưa một bản sao của giá trị trên đỉnh stack lên stackroll: lấy hai giá trị trên cùng ra và xoay một phần stack còn lại theo độ sâu và số lần được chỉ định- Nếu độ sâu là số âm thì đó là lỗi và lệnh bị bỏ qua
rollvượt quá độ sâu stack tối đa phụ thuộc triển khai là lỗi phụ thuộc triển khai, và khuyến nghị bỏ qua lệnhin: đọc một số hoặc ký tự từ STDIN và đưa lên stack- Nếu không có input, hoặc khi input số nguyên mà không nhận được số nguyên, thì đó là lỗi và lệnh bị bỏ qua
out: xuất giá trị trên đỉnh stack ra STDOUT dưới dạng số hoặc ký tự- Các phép toán không thể thực hiện do thiếu giá trị trên stack sẽ bị bỏ qua và chuyển sang lệnh tiếp theo
Ví dụ và công cụ
- Có thể xem các ví dụ Piet tại Sample programs
- Các trình thông dịch và công cụ phát triển bên ngoài được tổng hợp tại Third-party Piet interpreters and development tools
1 bình luận
Ý kiến trên Hacker News
Chương trình cuối cùng trong trang ví dụ thực sự đáng kinh ngạc: có người tên là Piet đã nhìn thấy một tác phẩm nghệ thuật gợi nhớ đến ngôn ngữ Piet rồi thử chạy nó.
Nó đã chạy được, và có lẽ đây là trường hợp đầu tiên trong lịch sử một họa sĩ đồ họa vô tình vẽ ra một chương trình máy tính hoạt động.
https://www.dangermouse.net/esoteric/piet/samples.html
https://gitlab.fabcity.hamburg/hofalab/piet-get-together
https://www.mcmillen.dev/sigbovik/
Piet chụp ảnh tác phẩm, chuyển nó thành tệp hình ảnh với các màu được hiệu chỉnh gần với bảng màu Piet, rồi chạy thử và nó thực sự chạy được; đoạn mã là một vòng lặp vô hạn đọc ký tự ASCII và in ra giá trị số ASCII tương ứng.
Điều này thật khó tin.
Câu giải thích “dĩ nhiên, nếu viết chương trình lớn hơn thì sẽ có được giá trị chính xác hơn” nghe với tôi như một kiểu đùa mà trước giờ tôi chưa từng thấy.
Theo đặc tả, trình thông dịch phải bắt đầu trượt theo DP hướng mới từ codel trắng hiện tại, và tiếp tục cho đến khi đi vào một khối màu hoặc gặp ràng buộc khác.
Nhưng trình thông dịch npiet lại nhìn qua khoảng trắng rồi tua ngược về vị trí codel màu cuối cùng. Tôi vẫn muốn một ngày nào đó thêm hành vi đó thành tùy chọn trong lexer của trình biên dịch Piet của mình, nhưng đến giờ vẫn chưa động vào.
Nếu theo đúng đặc tả, chương trình đó sẽ trở thành một vòng lặp không kết thúc khá đơn giản vì các góc cực trị của gần như mọi khối đều kề với màu trắng. Viết các chương trình Piet phức tạp nhắm đến nhiều trình thông dịch và trình biên dịch là việc khá khó, và mỗi cái đều có những khác biệt diễn giải tinh vi không được tài liệu hóa.
Tôi nghĩ đầu ra từ backend Piet của mình nhìn chung ít phụ thuộc vào trình thông dịch hơn, nhưng tôi cũng chỉ đào sâu với khoảng ba bốn trình thông dịch khác thôi.
https://github.com/boothby/repiet/
Lướt qua tài liệu thì có vẻ với điều kiện “những thao tác không thể thực hiện, như phép toán cần pop khi không đủ giá trị trên stack, sẽ פשוט bị bỏ qua và tiếp tục sang lệnh tiếp theo”, những hình ảnh kiểu này có thể đều chạy mà không lỗi.
Tuy vậy, trong số các hình ngẫu nhiên như thế, có bao nhiêu cái thực sự làm được điều gì đó “có ý nghĩa” lại là chuyện khác.
Piet là một thử nghiệm mang tính cột mốc ngay cả trong thế giới ngôn ngữ lập trình kỳ quặc, nhưng tôi thấy nó chưa đạt được mục tiêu làm cho chương trình trông như một bức tranh Mondrian trừ khi nhà phát triển thực sự cố ý như vậy.
Tôi ước chính cấu trúc của ngôn ngữ được thiết kế để bất kể “viết” gì thì kết quả cũng trông như tranh Mondrian.
Tôi luôn hay nảy ra câu hỏi này: thuật toán trông như thế nào?
Liệu có thể tạo ra ngoài đời thực thứ gì đó giống với những gì xuất hiện trong tiểu thuyết The Glass Bead Game của Hermann Hesse không? Tựa gốc là Magister Ludi
Là một người thiên về thị giác, tôi muốn tin là có thể, và thực tế cũng đã từng thử dùng những công cụ như vậy
https://community.carbide3d.com/uploads/default/original/3X/5/b/5b0872a5666fec9b7bb6fd623c431de03263372d.jpeg
Nhưng nếu không có câu trả lời rõ ràng cho câu hỏi trên, những công cụ kiểu này luôn có nguy cơ trở thành như sau
https://blueprintsfromhell.tumblr.com/
https://scriptsofanotherdimension.tumblr.com/
Cũng khó cân bằng giữa khả năng biểu đạt bằng hình ảnh và tính mô-đun, mà nếu đẩy mạnh tính mô-đun thì lại quá dễ quay về rào cản văn bản vốn muốn tránh
Sergei Lewis và tôi mỗi người đều đã làm công cụ tạo mã Piet. Trình assembler của Sergei tạo ra mã đẹp mắt hơn hẳn backend Piet của tôi
Điều thực sự thấy được trong đầu ra của trình biên dịch của tôi chỉ là tôi đã dùng trampoline một cách cực kỳ lười biếng
http://www.toothycat.net/wiki/wiki.pl?MoonShadow/Piet
https://github.com/boothby/repiet/
https://en.wikipedia.org/wiki/Trampoline_(computing)
Đây là ý tưởng tôi có được khi đọc sách của Steven Pinker: từ ngữ trừu tượng có thể được tách thành những từ đơn giản hơn, và cuối cùng sẽ mô tả một dạng quan hệ không gian nào đó. Ví dụ, “rekindle” có thể được hiểu là “đưa hai thứ lại với nhau một lần nữa”
Tương tự, vòng lặp for cũng là một khái niệm tinh thần về việc “một thứ đi qua nhiều thứ khác”, và điều này có biểu đạt trực quan như “100” -> “010” -> “001”
Vì vậy tôi tự hỏi liệu có thể tạo ra một ngôn ngữ định nghĩa những thành phần như thế này bằng các biến đổi trực quan thuần túy hay không
Mấy thứ như thế này rất hợp để xuất hiện trong phim trinh thám tội phạm, làm nhân vật chính hoặc điều tra viên bế tắc một lúc trước khi ai đó nhận ra đó là mã
Tôi từng nghĩ chỉ có mã QR mới hữu dụng theo kiểu đó
Có người đã tạo quine bằng Piet: http://mamememo.blogspot.com/2009/10/piet-quine.html?m=1
Ảnh trong bài đó đã bị hỏng, nhưng có bản sao ở đây: https://codegolf.stackexchange.com/a/23255/103045
Khoảnh khắc phát hiện ra Piet là một trải nghiệm đặc biệt pha trộn giữa cảm giác kính nể, bối rối và ngạc nhiên
Với tôi, điều đó được ghi lại trong cuộc trò chuyện này trên podcast khoa học máy tính “The CS Primer Show” với người bạn Oz của tôi: https://show.csprimer.com/episodes/e2-dont-let-a-gpt-have-all-the-fun
Ở đại học có một lớp học nhỏ về ngôn ngữ lập trình kỳ lạ
Mỗi người phải chọn một ngôn ngữ như Brainfuck, Piet rồi nghịch thử, và tôi chọn Piet, thấy khá thú vị
Thành thật mà nói, ứng dụng mẫu nhỏ tôi làm ra không thật sự xuất sắc về mặt thẩm mỹ, và có vẻ muốn tạo nghệ thuật bằng Piet thì phải trở thành chuyên gia Piet
Trang ví dụ rất tuyệt
Có thể thấy canvas dần tiến hóa để ngày càng tinh vi và đẹp mắt hơn
https://www.dangermouse.net/esoteric/piet/samples.html
Câu “light được coi là tối hơn dark một bậc” nghe khá thâm sâu
Sẽ rất hay nếu có thể tạo một autoencoder học cách nhận mã Python hoặc ngôn ngữ bớt kỳ quặc hơn rồi xuất ra Piet
Khi đó có lẽ còn có thể tạo ra các thuật toán ngẫu nhiên kiểu như Stable Diffusion