2011-12-23 28 views
14

Từ một ByteString là một constructor với ForeignPtr:độ tinh khiết của các chức năng tạo ByteString (hoặc bất kỳ đối tượng với thành phần ForeignPtr)

data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload 
        {-# UNPACK #-} !Int                -- offset 
        {-# UNPACK #-} !Int                -- length 

Nếu tôi có một hàm trả về ByteString, sau đó đưa ra một đầu vào, nói một hằng số Word8 , hàm sẽ trả về một ByteString với giá trị ForeignPtr không xác định - như giá trị mà sẽ được xác định bởi trình quản lý bộ nhớ.

Vì vậy, điều đó có nghĩa là hàm trả về ByteString không thuần túy? Điều đó dường như không rõ ràng như vậy, nếu bạn đã sử dụng các thư viện ByteString và Vector. Chắc chắn, nó sẽ được thảo luận rộng rãi nếu nó là trường hợp (và hy vọng hiển thị trên đầu trang của google tìm kiếm). Sự tinh khiết đó được thi hành như thế nào?

Lý do đặt câu hỏi này là tôi tò mò các điểm tinh tế liên quan đến việc sử dụng các đối tượng ByteString và Vector, từ phối cảnh trình biên dịch GHC, được đưa ra thành viên ForeignPtr trong hàm tạo của chúng là gì.

Trả lời

18

Không có cách nào để quan sát giá trị của con trỏ bên trong ForeignPtr từ bên ngoài mô-đun Data.ByteString; việc triển khai của nó là nội bộ không tinh khiết, nhưng bên ngoài tinh khiết, bởi vì nó đảm bảo rằng các bất biến cần thiết phải được giữ nguyên miễn là bạn không thể nhìn thấy bên trong nhà xây dựng ByteString - mà bạn không thể làm được.

Đây là một kỹ thuật phổ biến trong Haskell: triển khai một cái gì đó với các kỹ thuật không an toàn dưới mui xe, nhưng phơi bày một giao diện thuần túy; bạn có được cả hiệu suất và các kỹ thuật không an toàn về điện năng mang lại, mà không ảnh hưởng đến sự an toàn của Haskell. (Tất nhiên, các mô-đun triển khai có thể có lỗi, nhưng bạn có nghĩ rằng ByteString sẽ là ít hơn có khả năng bị rò rỉ nếu nó được viết bằng C? :))

Theo các điểm tinh tế, nếu bạn đang nói từ quan điểm của người dùng, đừng lo lắng: bạn có thể sử dụng bất kỳ chức năng nào mà các thư viện ByteString và Vector xuất ra mà không phải lo lắng, miễn là chúng không bắt đầu với unsafe. Cả hai thư viện này đều rất trưởng thành và được thử nghiệm tốt, vì vậy bạn không nên gặp phải bất kỳ vấn đề nào về tinh khiết và nếu bạn làm, đó là lỗi trong thư viện và bạn nên báo cáo.

Theo cách viết mã của riêng bạn cung cấp sự an toàn bên ngoài với việc triển khai nội bộ không an toàn, quy tắc rất đơn giản: duy trì tính minh bạch tham chiếu.

Lấy ByteString làm ví dụ, các hàm để tạo ByteStrings sử dụng unsafePerformIO để phân bổ khối dữ liệu, sau đó chúng biến đổi và đặt vào hàm tạo. Nếu chúng tôi đã xuất phương thức khởi tạo, thì mã người dùng sẽ có thể nhận được tại số ForeignPtr. Điều này có vấn đề không? Để xác định xem đó có phải là, chúng tôi cần tìm một hàm thuần túy (tức là không phải trong số IO) cho phép chúng tôi phân biệt hai ForeignPtr được phân bổ theo cách này. Một cái nhìn nhanh chóng tại the documentation cho thấy rằng có một chức năng như vậy: instance Eq (ForeignPtr a) sẽ cho phép chúng tôi phân biệt chúng. Vì vậy, chúng tôi không được cho phép mã người dùng truy cập vào ForeignPtr. Cách dễ nhất để làm điều này là không xuất khẩu hàm tạo.

Tóm tắt: Khi bạn sử dụng cơ chế không an toàn để thực hiện điều gì đó, hãy xác minh rằng tạp chất giới thiệu không thể rò rỉ bên ngoài mô-đun, ví dụ: bằng cách kiểm tra các giá trị bạn sản xuất với nó.

Theo như các vấn đề về trình biên dịch, bạn không nên lo lắng về chúng; trong khi các chức năng là không an toàn, chúng không được cho phép bạn làm bất cứ điều gì nguy hiểm hơn, ngoài vi phạm độ tinh khiết, hơn là bạn có thể thực hiện trong đơn IO để bắt đầu. Nói chung, nếu bạn muốn làm điều gì đó có thể tạo ra kết quả không mong muốn không mong muốn, bạn sẽ phải sử dụng unsafeDupablePerformIO nếu bạn có thể xử lý hai chủ đề đánh giá cùng một mẩu biểu mẫu unsafeDupablePerformIO m cùng một lúc. unsafePerformIO hơi chậm hơn unsafeDupablePerformIO vì nó ngăn điều này xảy ra. (Các phần tử trong chương trình của bạn có thể được đánh giá bởi hai luồng đồng thời trong quá trình thực hiện bình thường với GHC, điều này thường không phải là vấn đề, vì đánh giá cùng một giá trị thuần túy hai lần sẽ không có tác dụng phụ (theo định nghĩa), nhưng khi viết mã không an toàn, đó là điều bạn phải tính đến.)

GHC documentation for unsafePerformIO (và unsafeDupablePerformIO, như tôi đã liên kết ở trên) nêu chi tiết một số cạm bẫy bạn có thể gặp phải; tương tự như tài liệu cho unsafeCoerce# (nên được sử dụng thông qua tên di động của nó, Unsafe.Coerce.unsafeCoerce).

+0

Vâng, tôi dự định sử dụng các hoạt động không an toàn. Do đó, câu hỏi này :) Tôi sẽ muốn tìm hiểu về các vấn đề tôi cần phải nhận thức, như các nhà văn thư viện được. Những hiểu biết đó sẽ rất hữu ích, khi viết mã riêng của chúng ta cần phải nhanh chóng, nhưng vẫn tinh khiết bên ngoài, cho các phần mở rộng song song và đồng thời. – Sal

+0

Ah, OK; điều đó không rõ ràng với tôi từ câu hỏi. Tôi sẽ thử và kết hợp một số thông tin đó vào câu trả lời của tôi, mặc dù nó khó khăn vì quy tắc cơ bản chỉ là "đảm bảo tính minh bạch tham chiếu từ bên ngoài mô-đun". – ehird

+0

Tôi đã mở rộng nó một số chi tiết, hy vọng điều này sẽ giúp :) – ehird

Các vấn đề liên quan