2011-08-02 33 views
14

Giả sử tôi có một datatype Vector quy định như sau:Haskell: Ưu tiên phù hợp với mẫu hoặc truy cập thành viên?

data Vector = Vector { x :: Double 
        , y :: Double 
        , z :: Double 
        } 

Nó sẽ là bình thường hơn để xác định chức năng chống lại nó sử dụng truy cập thành viên:

vecAddA v w 
    = Vector (x v + x w) 
      (y v + y w) 
      (z v + z w) 

Hoặc sử dụng mô hình khớp:

vecAddB (Vector vx vy vz) (Vector wx wy wz) 
    = Vector (vx + wx) 
      (vy + wy) 
      (vz + wz) 

(Xin lỗi nếu tôi có bất kỳ thuật ngữ nào không chính xác).

+3

Chỉ cần cho đầy đủ: có cũng là một hình thức của mô hình kết hợp sử dụng các lĩnh vực kỷ lục: 'vecAddA (Vector {x = vx, y = vy, z = vz}) (Vector {x = wx, y = wy, y = wz}) = ... ' – hvr

Trả lời

12

Tôi thường sử dụng đối sánh mẫu, đặc biệt là vì bạn đang sử dụng tất cả đối số của hàm tạo và không có nhiều đối số. Ngoài ra, Trong ví dụ này, đó không phải là vấn đề, nhưng hãy xem xét các vấn đề sau:

data Foo = A {a :: Int} | B {b :: String} 

fun x = a x + 1 

Nếu bạn sử dụng mẫu phù hợp để làm việc với loại Foo, bạn an toàn; không thể truy cập thành viên không tồn tại. Nếu bạn sử dụng các hàm truy cập, mặt khác, một số thao tác như gọi fun (B "hi!") ở đây sẽ dẫn đến lỗi thời gian chạy. EDIT: trong khi nó tất nhiên khá có thể quên để phù hợp trên một số constructor, mô hình phù hợp làm cho nó khá rõ ràng rằng những gì xảy ra phụ thuộc vào những gì constructor được sử dụng (bạn cũng có thể nói với trình biên dịch để phát hiện và cảnh báo bạn về mẫu không đầy đủ) trong khi việc sử dụng một hàm gợi ý nhiều hơn rằng bất kỳ hàm tạo nào đi, IMO. Accessors được lưu tốt nhất cho các trường hợp khi bạn muốn nhận được chỉ một hoặc một vài đối số (có khả năng) của hàm tạo và bạn biết rằng nó an toàn để sử dụng chúng (không có nguy cơ sử dụng một trình truy cập trên hàm tạo sai, Ví dụ:

+1

Trên flipside, nếu bạn có 'dữ liệu Foo = A {f :: Int} | B {f :: Int} 'thì sẽ ngắn gọn hơn khi viết một dòng bằng cách sử dụng hàm' f' để trích xuất giá trị cho cả hai trường hợp (giả sử logic cho cả hai trường hợp giống nhau), thay vì viết hai dòng thành mẫu phù hợp với cả hai trường hợp. –

+0

Vâng, đó có thể là một ý tưởng hay đối với một kiểu như 'dữ liệu SomeData = TextData {length :: Int, text :: String} | BinaryData {length :: Int, blob :: [Word8]} '), trong đó hai đối số hàm tạo chia sẻ cả hai loại và mục đích. – valderman

+0

@Dan Burton: Trên flip-flipside, nếu sử dụng cùng một logic cho cả hai là hợp lý cho một loại như thế, hãy xem xét viết lại nó như là 'dữ liệu FooCase = A | B' và 'dữ liệu Foo = Foo {fooCase :: FooCase, f :: Int}', cũng cung cấp cho bạn một cách dễ dàng để kiểm tra A so với B mà không làm phiền phần còn lại. –

4

Đây là một ưu tiên thẩm mỹ vì cả hai đều tương đương ngữ nghĩa. Vâng, tôi cho rằng trong một trình biên dịch ngây thơ, một trình biên dịch đầu tiên sẽ chậm hơn vì các cuộc gọi hàm, nhưng tôi có một thời gian khó tin rằng nó sẽ không được tối ưu hóa trong cuộc sống thực.

Tuy nhiên, chỉ với ba yếu tố trong hồ sơ, vì bạn đang sử dụng cả ba phần tử và có lẽ là một số ý nghĩa của đơn đặt hàng là, tôi sẽ sử dụng thứ hai. Một đối số thứ hai (mặc dù yếu hơn) là cách này bạn đang sử dụng thứ tự cho cả bố cục và phân hủy, chứ không phải là một hỗn hợp của truy cập trường và thứ tự.

+3

Đồng ý, không có sự khác biệt chức năng giữa hai, và tôi không (ở giai đoạn này) lo lắng về hiệu suất. Tôi quan tâm đến việc liệu một Haskeller có kinh nghiệm hơn sẽ xem xét một trong những điều này và nghĩ rằng "đó là một cách kỳ lạ để viết nó". – stusmith

7

Một đối số nhỏ "thế giới thực" khác: Nói chung, không nên có tên mục ghi ngắn như vậy, vì các tên ngắn như xy thường được sử dụng cho địa phương biến.

Vì vậy, các "công bằng" so sánh ở đây sẽ là:

vecAddA v w 
    = Vector (vecX v + vecX w) (vecY v + vecY w) (vecZ v + vecZ w) 
vecAddB (Vector vx vy vz) (Vector wx wy wz) 
    = Vector (vx + wx) (vy + wy) (vz + wz) 

Tôi nghĩ rằng mô hình phù hợp với chiến thắng trong hầu hết trường hợp thuộc loại này. Một số trường hợp ngoại lệ đáng chú ý:

  • Bạn chỉ cần truy cập vào một hoặc hai lĩnh vực trong một bản ghi lớn hơn
  • Bạn muốn duy trì linh hoạt để thay đổi bản ghi sau đó, chẳng hạn như bổ sung thêm các lĩnh vực (hoặc thay đổi!).
+0

Đến điểm thứ hai, "Bạn muốn linh hoạt để thay đổi bản ghi sau, chẳng hạn như thêm nhiều trường.", Đây cũng có thể là một lý do để gắn bó với (vị trí) khớp mẫu, khi đó bạn phải đi qua từng phù hợp với mẫu để thực hiện để đảm bảo bạn không quên cập nhật các chức năng của mình xử lý các trường bổ sung (đối với các trường hợp, hãy nghĩ về các cá thể 'NFData' được viết theo cách thủ công) – hvr

4

(Cảnh báo, có thể sai.Tôi vẫn là một người mới của Haskell, nhưng đây là sự hiểu biết của tôi)

Một điều mà người khác chưa đề cập là khớp mẫu sẽ làm cho hàm "nghiêm ngặt" trong đối số của nó. (http://www.haskell.org/haskellwiki/Lazy_vs._non-strict)

Để chọn mẫu để sử dụng, chương trình phải giảm đối số WHNF trước khi gọi hàm, trong khi sử dụng bản ghi- hàm accessor cú pháp sẽ đánh giá đối số bên trong hàm. Tôi không thể đưa ra bất kỳ ví dụ cụ thể nào (vẫn là một newbie) nhưng điều này có thể có ý nghĩa về hiệu suất trong đó các đống "đống" khổng lồ có thể tích tụ trong các hàm đệ quy, không nghiêm ngặt. (Điều đó có nghĩa là, đối với các hàm đơn giản như trích xuất các giá trị, sẽ không có sự khác biệt về hiệu năng).

(ví dụ bê tông rất hoan nghênh)

Nói tóm lại

f (Just x) = x 

là thực sự (sử dụng BangPatterns)

f !jx = fromJust jx 

Edit: Trên đây là không một ví dụ tốt về tính nghiêm minh , bởi vì cả hai đều thực sự nghiêm ngặt từ định nghĩa (f bottom = bottom), chỉ để minh họa những gì tôi có ý nghĩa từ hiệu suất ide.

+0

+1 Bạn nói đúng. Tôi thậm chí còn bỏ lỡ bản thân mình trong lần đọc đầu tiên. Tôi đã đưa ra một ví dụ hơi rõ ràng hơn trong câu trả lời của tôi. – hammar

2

Như kizzx2 pointed out, có một sự khác biệt tinh tế trong tính nghiêm minh giữa vecAddAvecAddB

vecAddA ⊥ ⊥ = Vector ⊥ ⊥ ⊥ 
vecAddB ⊥ ⊥ = ⊥ 

Để có được ngữ nghĩa tương tự khi sử dụng kết hợp hoa văn, người ta sẽ phải sử dụng các mẫu không thể chối cãi.

vecAddB' ~(Vector vx vy vz) ~(Vector wx wy wz) 
    = Vector (vx + wx) 
      (vy + wy) 
      (vz + wz) 

Tuy nhiên, trong trường hợp này, các lĩnh vực Vector nên có lẽ nghiêm khắc để bắt đầu với cho hiệu quả:

data Vector = Vector { x :: !Double 
        , y :: !Double 
        , z :: !Double 
        } 

Với lĩnh vực chặt chẽ, vecAddAvecAddB ngữ nghĩa tương đương.

+0

Như bạn đã đoán, chúng thực sự nghiêm ngặt trong mã gốc. Tôi bỏ qua điều đó để cố đơn giản hóa câu hỏi. – stusmith

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