1 điểm bởi GN⁺ 2024-11-14 | 1 bình luận | Chia sẻ qua WhatsApp
  • Raku giống một phòng thí nghiệm tính năng ngôn ngữ để trực tiếp thử nghiệm nhiều ý tưởng hơn là một thiết kế đơn nhất đã được hoàn thiện, nên có thể thấy ở một nơi những thử nghiệm mà các ngôn ngữ khác có thể áp dụng
  • Junctions lan truyền phép toán lên một tập các giá trị khả dĩ rồi gấp lại bằng các lượng từ Boolean như all, any, one, giúp diễn đạt ngắn gọn các điều kiện phức tạp
  • * trong Whatevers được dùng cho phạm vi vô hạn, hàm ẩn danh và lập chỉ mục dựa trên độ dài danh sách; cú pháp này ngắn gọn nhưng tạo ra phong cách code lạ mắt
  • Regex của Raku từ bỏ tương thích PCRE, đổi lại cung cấp ngữ pháp có thể hợp thành, bộ phân tách giá trị, lookahead dễ đọc và mặc định bỏ qua khoảng trắng
  • Nhìn từ Hyperoperators, Pair Syntax, Slangs, RakuAST đến Rakudo Star, Raku là một ngôn ngữ thử nghiệm rộng từ cú pháp đến cách phân phối

Vì sao Raku trông giống một phòng thí nghiệm tính năng ngôn ngữ

  • Sau hơn một năm thực sự sử dụng Raku, Raku có vẻ gần với một “phòng thí nghiệm dành cho tính năng ngôn ngữ” hơn
  • Năm mô hình đồng thời và nhiều cách để làm cùng một việc cho thấy tính chất thử nghiệm “để xem chuyện gì sẽ xảy ra”
  • Lý do nhiều tính năng tương tác với nhau một cách kỳ lạ và có nhiều trường hợp biên cũng là vì việc tinh chỉnh để 100 thí nghiệm cùng hoạt động với nhau khó hơn rất nhiều so với việc chạy chúng
  • Các tính năng được bàn ở đây là những thử nghiệm kiểu Raku mà một ngày nào đó ta có thể thấy trong các ngôn ngữ lập trình khác

Junctions: chồng các giá trị khả dĩ và lượng từ Boolean

  • Junctions biểu diễn sự chồng lớp của các giá trị khả dĩ; khi áp dụng phép toán lên junction, cùng phép toán đó được áp dụng lên toàn bộ các giá trị bên trong
    • 2|10 trở thành any(2, 10)
    • 2&10 + 3 trở thành all(5, 13)
    • (1&2) + (10^20) được tổ hợp thành dạng như all(one(11, 21), one(12, 22))
  • Công dụng cốt lõi của Junctions là biểu diễn công thức Boolean như all, any
    • Không có cách destructure một junction
    • Muốn dùng trong thực tế thì trước hết phải gấp nó về Boolean
  • so ép junction thành Boolean
    • so (1&2) + (10^20) < 15True
    • so (1&2) + (10^20) > 0False
  • list.all của Raku không phải là method nhận đối số, mà là method không đối số biến danh sách thành junction
    • <1 2 3>.all trở thành all(1, 2, 3)
    • Khi áp dụng is-prime($x), nó được áp dụng cho từng giá trị như all(False, True, True)
  • Kết hợp Junctions cũng có thể tạo ra hành vi như unique
    • Có thể diễn đạt “mọi phần tử có đúng một phần tử bằng nó hay không” bằng {.all == .one}
    • <1 2 3 7> là đúng, còn <1 2 3 7 2> là sai

Whatevers: phạm vi vô hạn và hàm ẩn danh tạo bằng *

  • * trong Whateverský hiệu whatever đảm nhiệm nhiều vai trò trong Raku
  • Trong phạm vi hoặc chuỗi, * nghĩa là không có biên
    • 1..*1..Inf
    • (2,4,8...*)[17]262144
  • Biểu thức chứa * được nâng thành hàm ẩn danh
    • Hành vi này được gọi là whatever-priming
    • Kết quả là WhateverCode, và ngoài kiểu ra thì không khác gì các hàm khác
    • (* + 10)(2)12
    • (^10).map(* % 2) tạo ra mẫu 0 1 0 1 ...
  • Nếu trong biểu thức có hai *, mỗi cái trở thành một đối số vị trí riêng
    • (2, 30, 4, 50).map(* + *) trả về (32, 54)
    • Hành vi này hữu ích cho các định nghĩa Fibonacci khó viết, nhưng không chắc là tốt hơn cách mỗi * cùng nhận một giá trị
  • Khá nhiều method của Raku có thể nhận hàm, nên priming được dùng rộng rãi
    • l[*-1] trông giống chỉ mục âm, nhưng thực ra [] truyền độ dài danh sách vào hàm để tính chỉ mục cần truy xuất
    • Nếu độ dài danh sách là 10, l[*-1] trở thành l[10-1], tức phần tử cuối
    • l.head(2) nghĩa là hai phần tử đầu, còn l.head(*-2) nghĩa là toàn bộ trừ hai phần tử cuối
  • Nếu truyền hàm khác vào [], cũng có thể triển khai cấu trúc như ring buffer một cách đơn giản
    • @x[95 % *]-- tính vị trí bằng độ dài danh sách

Regex: ngữ pháp có thể hợp thành thay cho tương thích PCRE

  • Raku dùng cú pháp regex mới khác với regex POSIX hoặc PCRE
    • Regex POSIX có cấu trúc nhiều dấu backslash và dấu câu
    • PCRE tương thích ngược với POSIX và kế thừa cấu trúc backslash/dấu câu phức tạp hơn
    • Perl 6 phá vỡ tương thích ngược và đưa vào cú pháp regex mới
  • Cải tiến nổi bật nhất là khả năng hợp thành
    • Trong nhiều ngôn ngữ, người ta nối các chuỗi regex để kết hợp chúng
    • Raku chèn regex khác như /< foo >+/, nhờ đó match regex foo một hoặc nhiều lần mà không bị rò rỉ ra regex cha
  • Có thể chia regex phức tạp thành các phần nhỏ hơn, dễ đọc hơn
  • Khi phá vỡ tương thích ngược, nhiều tính năng tiện dụng cũng được thêm vào
    • \d+ % ',' match 1, 1,2, 1,1,4 nhưng không match 1, hay 12
    • Lookahead và non-capturing group không còn là tổ hợp ký hiệu khó hiểu
    • r1 && r2 chỉ match những chuỗi thỏa mãn cả r1r2
    • Có thể dừng backtracking bằng :
    • Khoảng trắng mặc định bị bỏ qua và phải được bật rõ ràng trong mẫu match
  • Không tính riêng năng lực xử lý thực tế, ký pháp regex của Raku có thể được xem là một tính năng có khả năng xuất hiện trong các ngôn ngữ khác

Hyperoperators: phép map tổng quát đi sâu tới cả danh sách lồng nhau

  • Dạng cơ bản của Hyperoperators, l>>.method, giống map nhưng đệ quy đi xuống các danh sách con
    • [1, [2, 3], 4]>>.succ[2 [3 4] 5]
  • Trong Raku, lời gọi hàm dạng f(list, *args) có thể được chuyển thành dạng method list.&f(* args)
    • Vì vậy >>. hoạt động như một toán tử map tổng quát
    • Có thể dùng cùng Whatever như [1, [2, 3], 4]>>.&(*+1)
  • Hyperoperator nhị phân l1 << op >> l2 áp dụng op theo từng phần tử lên hai danh sách
    • Danh sách ngắn hơn được lặp lại cho đến khi danh sách dài hơn kết thúc
    • [1, 2, 3, 4, 5] <<+>> [10, 20][11 22 13 24 15]
    • Dạng << op<< hoặc >>op>> chỉ lặp cho đến khi danh sách bên trái hoặc bên phải kết thúc
  • Nó cũng hoạt động với giá trị đơn và danh sách lồng nhau
    • [1, 2, 3, 4, 5] <<+>> 10[11 12 13 14 15]
    • [1, [2, 3], 4, 5] <<+>> [10, 20][11 [22 23] 14 25]
  • Hyperoperator trên hai hash còn có hành vi riêng: áp dụng op cho hợp, giao và hiệu hash
  • Tên Hyperoperator xuất phát từ việc mọi phép toán được đánh giá song song
    • Vòng lặp tường minh cũng có thể được song song hóa bằng tiền tố hyper

Pair Syntax: đơn vị cơ bản của hash và cách truyền flag ngắn gọn

  • Pair là đơn vị cơ bản cấu thành hash trong Raku
    • Hash là tập hợp các cặp key => value
    • Pair là kiểu cơ bản, còn hash là tập hợp các pair
  • Raku có nhiều colon syntax để chỉ định pair ngắn gọn
    • my $x = 3; :$xx => 3
    • :a<$x>a => "$x"
    • :a($x)a => 3
    • :3aa => 3
  • Cách viết tắt quan trọng nhất là :key:!key
    • Chúng lần lượt được ánh xạ thành key => True, key => False
    • Được dùng như một cách ngắn gọn để gắn flag cho method
  • Method match nhận nhiều colon pair như flag
    • Dùng các dạng như :continue(:$c), :pos(:$p), :global(:$g), :overlap(:$ov)
    • :f(:$foo) trong signature định nghĩa tham số $foo đồng thời tạo alias :f
    • Người gọi có thể đặt cùng một flag bằng :f hoặc :foo
  • Đối số colon pair linh hoạt về thứ tự và vị trí
    • "abab".match(/../, :g) thực hiện match toàn cục
    • "abab".match(/../, :g, :ov) thực hiện match chồng lấn
    • Có thể đổi thứ tự và viết liền như "abab".match(:g:ov, /../)
    • Cũng có thể đặt đối số keyword sau lời gọi hàm như "abab".match(:g, /../):ov:2nd
  • Cú pháp này cho phép chỉ định option của method ngắn hơn nhiều so với match(global=True, overlap=True)

Slangs, RakuAST, Rakudo Star

  • Slangs là tính năng thay đổi cú pháp Raku
  • RakuAST là tính năng phân tích biểu thức Raku thành AST bên trong Raku
    • Q/my $x; $x++/.AST hiển thị khai báo biến và phép tăng postfix dưới dạng RakuAST::StatementList
    • Có ví dụ xuất code Raku theo ký pháp kiểu ngôn ngữ khác như Q/my $x; put $x/.AST.DEPARSE("NL")
  • Raku có bản cài đặt Rakudo Star
    • Bản phân phối này cài sẵn và cung cấp các module bên thứ ba được chọn lọc
    • Nó giống một thỏa hiệp giữa gánh nặng bảo trì của thư viện chuẩn lớn và gánh nặng người dùng phải tự tìm package phù hợp trong hệ sinh thái

1 bình luận

 
GN⁺ 2024-11-14
Ý kiến trên Hacker News
  • Trước đây tôi đã tự triển khai cho JavaScript một thứ tương tự tính năng biểu thức chính quy kiểu tổ hợp từng được nói đến ở đây, và đến giờ sau vài năm, đó vẫn là một trong những thư viện tôi hay mang sang các dự án khác nhất
    Nó khiến cảm giác như có parser hạng nhất ngay trong ngôn ngữ trở nên gần hơn một chút; chẳng hạn tôi đã dùng nó để hiện thực việc phân tích media type bằng biểu thức chính quy: https://runkit.com/tolmasky/media-type-parsing-with-template...
    Trên npm là templated-regular-expression, GitHub: https://github.com/tolmasky/templated-regular-expression
    Tất nhiên, ngôn ngữ lập trình nên có parser thật sự và không nên dùng regex làm parser, nhưng nếu đằng nào cũng dùng regex thì việc có thể chia nó thành các mảnh nhỏ thật sự rất tuyệt

    • “Parser thật sự” không đủ mạnh để phân tích Raku
      Regex của Raku khi kết hợp với grammar thì mạnh hơn rất nhiều, và nếu viết tốt thì còn dễ hiểu hơn bất kỳ “parser thật sự” nào. Để phân tích Raku bằng parser thông thường, bạn phải có khả năng thêm và xóa các mục khỏi parser trong lúc đang parse; parser của Raku xử lý việc này bằng cách subclass grammar hiện tại để thêm/xóa mục trong phạm vi từ vựng hiện tại, rồi khi phạm vi kết thúc thì quay lại grammar trước đó
      Trong Raku, regex là một cú pháp khác để viết code, chỉ khác cú pháp và hành vi mặc định một chút so với cú pháp cơ bản, và có thể có tham số cũng như biến. Nếu cú pháp regex không phù hợp với tác vụ nào đó, bạn có thể chèn cú pháp Raku thông thường vào để làm việc cần làm, rồi quay lại cú pháp regex
      Ví dụ, có thể khớp một hoặc nhiều A theo sau bởi cùng số lượng B và cùng số lượng C; trong hầu hết cú pháp regex, để khớp cùng số lượng AB thì cần đệ quy ở giữa, nhưng làm vậy sẽ khó xử lý C theo cùng cách và cũng khó đọc hơn nhiều. Cách của Raku không cần backtracking hay đệ quy nên có thể chạy khá nhanh
      Nếu gom lại bằng grammar, bạn sẽ có được cây phân tích đầy đủ; không dùng grammar vẫn có thể làm được nhưng dùng grammar thì dễ hơn. Một ví dụ parser thực tế mà tôi thường khuyên xem là JSON::TINY::Grammar: https://github.com/moritz/json/blob/master/lib/JSON/Tiny/Gra...
      Nói thật, phần lớn thiết kế của “parser thật sự” có vẻ là sản phẩm phụ từ giới hạn RAM của các máy tính đời đầu. Bước token hóa riêng biệt là để giảm lượng RAM mà mã nguồn chiếm dụng, chừa bộ nhớ cho phân tích ngữ nghĩa và biên dịch về sau; tôi không thấy nó đơn giản hóa các bước sau đó nhiều đến thế. Module JSON::Tiny ở trên tạo các cấu trúc dữ liệu Raku native bằng action class trong khi grammar đang parse, nên có thể xem là vừa parse vừa biên dịch
  • Tôi đang dùng Raku trong production. Đây là ngôn ngữ tốt nhất cho xử lý văn bản, và trải nghiệm tạo parser thì quá tuyệt
    Thật ngạc nhiên khi nó không phải là ngôn ngữ hàng đầu để xây dựng pipeline văn bản cho LLM

    • Hơi muộn nhưng tôi tò mò không biết bạn có biết ví dụ hay nào về việc dùng Raku để gọi HTTPS API rồi polling cho đến khi trả về một giá trị cụ thể không
    • Tôi cũng tò mò bạn có đang dùng gói LLM nào của Raku không, nếu có thì dùng gói nào
  • Wow. Tôi muốn rời ngành trước ngày phải bảo trì một codebase Raku

    • Thú vị là sau khi đọc bài blog đó, tôi lại muốn nghỉ việc rồi tìm đến đội Raku để làm. Có lẽ tôi vẫn còn quá ngây thơ :)
    • Nếu chưa từng thấy code Raku thông thường thì phản ứng như vậy sau khi đọc bài là dễ hiểu
      Nhìn vào các sách Raku nhập môn, nhiều chỗ có cảm giác như Python gắn thêm cú pháp họ C. Nó dùng cú pháp nhiều dấu ngoặc nhọn, nhưng về độ tiện dụng, cấu trúc dữ liệu tích hợp và tính năng hướng đối tượng thì đều ở mức cao
      Nếu bạn biết các ngôn ngữ script cấp cao khác, tôi nghĩ code Raku cho các script tương tự sẽ khá dễ đọc. Tôi không nghĩ đa số người dùng sẽ dùng các tính năng thật sự kỳ lạ trong code hằng ngày; Raku linh hoạt hơn, nhưng code thông thường không đến mức trông khó hiểu
    • Giống Perl. Không ai muốn bảo trì, nhưng viết thì thật sự rất vui. Khả năng biểu đạt rất lớn
      Có thể thấy điều đó trong Raku qua khả năng định nghĩa đối số keyword dạng rút gọn, ví dụ :global(:$g), hay khả năng gọi khớp regex toàn cục như match(/foo/, :g) bằng cách giả định giá trị True. Perl cũng có nhiều thứ như vậy; tất cả đều nhằm viết nhanh hơn và vui hơn, nhưng kém dễ đọc hơn với người mới
    • Thật lạ là mọi người cũng nói điều tương tự về việc bảo trì codebase viết với hỗ trợ AI
      Có lẽ giờ đây nó sẽ trở thành khác biệt thế hệ. Cả một thế hệ lập trình viên lớn tuổi có thể sẽ cảm thấy mình bị gạt khỏi môi trường làm việc mà thế hệ hiện tại xem là bình thường
    • Tôi không nghĩ Raku là ngôn ngữ được chủ đích dành cho “ngành công nghiệp”
  • Một vài tính năng thì nửa quen nửa lạ. Hyper nghe giống một phiên bản ngẫu hứng hơn của thứ gì đó bên recursion-schemes, còn * được chỉ ra ở đây thì phần nào giống _ của Scala
    Tôi thích dùng _ trong lambda, nên mong mọi ngôn ngữ đều áp dụng thứ gì đó tương tự

  • > (2, 30, 4, 50).map(* + *) returns (32, 45) chẳng phải nên là returns (32, 54) sao? Vì mục thứ hai sẽ là 4+50
    Có lẽ đây là kết quả dịch nhầm mở đầu bắt nguồn từ việc ở một số nước người ta nói kiểu “four and fifty” như vierenvijftig, thay vì fifty-four trong tiếng Anh

    • Tôi kiểm tra trên rakudo thì thực tế nó trả về (32 54). Có vẻ tác giả gõ nhầm
  • Vậy giờ Perl là chất dẫn nhập để bước vào các ngôn ngữ họ APL à?

    • Đúng. Hơn nữa bài viết còn chưa đề cập đến meta-operator
      Ví dụ có thể dùng meta-operator reduce [ ] cùng với toán tử trung tố + để “cộng tất cả”: [+] 1, 2, 3
  • Raku vẫn có vấn đề lớn về tốc độ. Phân tích file log bằng biểu thức chính quy vốn là sở trường của Perl, nhưng ngay cả Raku mới nhất, nếu không tính thời gian khởi động, cũng mất lâu hơn Python 3.13 tới 6,5 lần

    • Cần đưa kèm ví dụ. Theo kinh nghiệm của tôi, có trường hợp Raku nhanh hơn và cũng có trường hợp chậm hơn, nên nếu chỉ nói “Raku mất lâu hơn Python 3.13 tới 6,5 lần” mà không xem nó chậm ở đâu thì không có nhiều ý nghĩa
  • Việc (2,4,8...*)[17] cho ra 262144 thật sự gây ngạc nhiên

    • Trong đầu tôi lập tức hiện lên từ “kinh khủng”, rồi tiếp đó là “kết quả kinh khủng” và bài toán dừng, nhưng mỗi người có thể có gu khác nhau
    • Việc phát hiện bước tăng đã có trong Perl6 từ lâu, nên không phải tính năng mới
      Nếu bỏ qua Whatever, điểm mới sau Perl6/Raku có lẽ nằm ở đánh giá trì hoãn
    • Khá nhất quán
      (1,3,9...*)[4,5] cho ra (81 243), còn (1,3,9...*)[(1..3)] cho ra (3 9 27)
      Cũng có thể lồng nhau, nên có thể viết những biểu thức như (1,2,4...*)[(1,2,4...*)[1,2,3]]
  • Bài viết đã chọn ra và bàn về những tính năng thú vị của Raku
    Tôi cho rằng các tính năng Raku quan trọng nhất là giao diện dòng lệnhgrammar
    Hỗ trợ giao diện dòng lệnh là tính năng phổ biến, ví dụ có các bản triển khai và trường hợp áp dụng docopt. Nhưng trong Raku, tính năng này được tích hợp sẵn và dễ dùng
    Trong khi đó, grammar là một tính năng hiếm ở chỗ ngôn ngữ lập trình cung cấp grammar như thành phần hạng nhất, và cho phép tạo cũng như kết hợp grammar bằng lập trình hướng đối tượng

  • Tôi đã theo dõi dự án này trong vài năm và thấy nó thú vị, nhưng thật sự tiếc là Perl 6 dường như đã lạc đường quá xa trong chuyến du hành xứ sở gương soi này

    • Tôi tò mò không biết Patrick Michaud dạo này đang làm gì và như thế nào