1 điểm bởi GN⁺ 3 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong guard của Elixir, chỉ cần đổi thứ tự điều kiện or cũng có thể khiến kết quả của đoạn code trông như cùng một biểu thức logic bị thay đổi
  • Thứ tự is_integer(x) or is_map_key(x,:foo) sẽ thực hiện đánh giá ngắn mạch trước với đầu vào là số nguyên, bỏ qua phép kiểm tra nguy hiểm
  • Ngược lại, is_map_key(x,:foo) or is_integer(x) với đầu vào là số nguyên sẽ không trả về false ở điều kiện đầu mà thất bại, nên không đi tiếp tới điều kiện sau
  • Vì khác biệt này, Foo.a(%{foo: 21}), Foo.a(37), Foo.b(%{foo: 21})true, nhưng Foo.b(37)false
  • Trông như tính giao hoán của phép toán Boolean bị phá vỡ, nhưng or có đánh giá ngắn mạch vốn chịu ảnh hưởng bởi thứ tự điều kiện; theo Elixir 1.20.1 và OTP 29 thì không có cảnh báo

Ví dụ thứ tự điều kiện làm thay đổi kết quả

  • Module ví dụ Foo định nghĩa hai hàm a/1b/1
    • a/1: kiểm tra guard theo thứ tự is_integer(x) or is_map_key(x, :foo)
    • b/1: kiểm tra guard theo thứ tự is_map_key(x, :foo) or is_integer(x)
    • Nếu guard khớp thì trả về true, nếu không thì mệnh đề tiếp theo trả về false
  • a/1: khi điều kiện an toàn đứng trước

    • Foo.a(%{foo: 21}) trở thành true
      • is_integer(x)false
      • is_map_key(x, :foo)true
      • Kết quả của ortrue, nên mệnh đề đầu tiên khớp
    • Foo.a(37) cũng trở thành true
      • is_integer(x)true
      • or được đánh giá ngắn mạch, is_map_key(x, :foo) không được thực thi
  • b/1: khi điều kiện có thể thất bại đứng trước

    • Foo.b(%{foo: 21}) trở thành true
      • is_map_key(x, :foo)true
      • is_integer(x) phía sau không được thực thi
    • Foo.b(37)false
      • Điều kiện đầu tiên is_map_key(x, :foo) thất bại thay vì trả về false
      • Sự thất bại của một hàm guard không được chuyển thành false, mà làm toàn bộ biểu thức guard thất bại
      • is_integer(x) phía sau không được gọi và mệnh đề đầu tiên cũng không khớp

Đánh giá ngắn mạch và việc không có cảnh báo

  • Với nhiều lập trình viên Elixir, hành vi này có thể trông như tính giao hoán của toán tử Boolean bị phá vỡ
  • Tuy nhiên, vì or thực hiện đánh giá ngắn mạch, không thể cho rằng đổi vị trí hai điều kiện thì luôn cho cùng kết quả
  • Môi trường tham chiếu là Elixir 1.20.1, OTP 29, và có vẻ Elixir không cảnh báo về vấn đề này

1 bình luận

 
Các ý kiến trên Lobste.rs
  • Tôi không phải lập trình viên Elixir, nhưng điều gây ngạc nhiên nhất ở ví dụ cuối là lỗi trong biểu thức guard không được truyền ngược về caller, mà guard đó bị “bỏ qua”
    Tôi nghĩ mình hiểu vì sao họ thiết kế như vậy, nhưng cũng không ngạc nhiên khi nó dẫn đến kết quả trái trực giác

  • Thật mỉa mai nếu nghĩ rằng thiết kế API của Erlang vốn nhằm hỗ trợ lập trình có chủ đích như Armstrong nói trong luận án Erlang p109/s4.5
    Trong luận án, ông giải thích việc tách các hàm như dict:fetch(Key, Dict), dict:search(Key, Dict), dict:is_key(Key, Dict) để thể hiện ý định của lập trình viên: “khóa bắt buộc phải tồn tại”, “khóa có thể tồn tại nên rẽ nhánh luồng xử lý”, “chỉ kiểm tra sự tồn tại”
    Nhưng is_map_key/2 của Elixir lại ném ngoại lệ nếu đối số “dict” không phải dict, và lỗi ngoại lệ đó khiến toàn bộ mệnh đề guard thất bại, dường như phá vỡ sự phân biệt này
    Ngược lại, nếu có một ngôn ngữ trong đó or bắt ngoại lệ rồi gộp thành false, thì ở các trường hợp khác có lẽ còn gây ngạc nhiên hơn

  • Nhờ cuộc thảo luận này mà tôi từng xem trước đây, tôi đã sẵn sàng giải câu đố lần này, và khi đó đã học được vài điều

    • Chính cuộc thảo luận đó đã truyền cảm hứng để tôi viết bài này
  • Có học được điều mới, nhưng tôi vẫn tiếc vì sao lại tránh tham chiếu tới Pratchett
    Chắc đâu đó Death đang ôm trán
    Điểm thú vị ở đây có hai phần: một guard thất bại, chứ không phải false, sẽ làm toàn bộ biểu thức thất bại; và hơi trái trực giác là is_map_key không bao hàm kiểm tra is_map
    Nếu thêm biến thể thứ ba như is_map(x) and is_map_key(x, :corporal) thì nó hoạt động đúng như mong đợi
    Hành vi của is_map_key trông hơi thiếu nhất quán nên gây bất ngờ, và có lẽ sẽ thú vị nếu kiểm tra các guard is_... khác để xem cái nào an toàn, cái nào phải được đánh giá với kỳ vọng về kiểu dữ liệu

    • Tôi đồng ý về tham chiếu tới Pratchett, nhưng hiện đang nắng nóng cực độ nên não tôi không hoạt động như mong đợi
    • Vì tò mò nên tôi tự kiểm tra vài trường hợp, và nhìn chung có vẻ is_map_keyguard is_ duy nhất yêu cầu một loại đối số cụ thể
      Các hàm is_ khác đều mang tính boolean và luôn trả về true | false, không thất bại
  • Ở đây nảy sinh một câu hỏi thú vị về phong cách Elixir
    Ví dụ thì vui và phần giải thích cũng tốt, nhưng cá nhân tôi nếu có thể sẽ thích pattern matching hơn guard
    Tất nhiên vẫn có ngoại lệ, nhưng những hàm kiểu này thường tôi sẽ viết thành nhiều mệnh đề hàm như def a(%{foo: _x}), do: true, def a(x) when is_integer(x), do: true, def a(_), do: false

  • Cũng đáng xem kèm: https://learnyouahaskell.github.io/syntax-in-functions.html/…

    • Guard của Haskell hơi khác
      Trong Haskell, bạn có thể gọi hàm tùy ý bên trong guard, còn Erlang thì giới hạn tập hàm được phép dùng trong đó