2009-11-05 31 views
22

Trình lập trình C++ cố gắng tìm hiểu Haskell tại đây. Xin vui lòng giải thích câu hỏi này có lẽ dễ dàng. Tôi muốn dịch một chương trình đại diện cho hình dạng 3D. Trong C++ Tôi có một cái gì đó như:Thừa kế để mở rộng cấu trúc dữ liệu trong Haskell

class Shape { 
public: 
    std::string name; 
    Vector3d position; 
}; 

class Sphere : public Shape { 
public: 
    float radius; 
}; 

class Prism : public Shape { 
public: 
    float width, height, depth; 
}; 

Tôi cố gắng để dịch này để Haskell (sử dụng hồ sơ?) Để tôi có thể có một số chức năng mà biết làm thế nào để hoạt động trên một Shape (như truy cập vào tên và vị trí của nó) và những người khác không chỉ biết cách vận hành trên các lĩnh vực, như tính toán thứ gì đó dựa trên vị trí và bán kính của nó.

Trong C++, chức năng thành viên chỉ có thể truy cập các tham số này nhưng tôi đang gặp khó khăn trong việc tìm hiểu cách thực hiện điều này trong Haskell với bản ghi hoặc loại lớp hoặc bất kỳ thứ gì.

Cảm ơn.

Trả lời

13

Trái ngược với xu hướng không khuyến khích sử dụng máy chữ, tôi khuyên bạn nên tìm hiểu cả giải pháp mà không có máy đánh chữ và để có được cảm giác về sự cân bằng khác nhau của các cách tiếp cận khác nhau.

Giải pháp "datatype đóng" duy nhất chắc chắn là "chức năng" hơn so với máy chữ. Nó ngụ ý rằng danh sách các hình dạng của bạn là "cố định" bởi mô-đun hình dạng của bạn và không mở rộng với các hình dạng mới từ bên ngoài. Nó vẫn còn dễ dàng để thêm chức năng mới hoạt động trên các hình dạng.

Bạn có một chút bất tiện ở đây nếu bạn có một chức năng chỉ hoạt động trên một loại hình duy nhất bởi vì bạn từ bỏ trình biên dịch tĩnh kiểm tra xem hình dạng được truyền vào có đúng không cho hàm (xem ví dụ của Nathan). Nếu bạn có rất nhiều hàm một phần này chỉ hoạt động trên một hàm tạo của kiểu dữ liệu của bạn, tôi sẽ xem xét lại cách tiếp cận này.

Đối với giải pháp kiểu chữ, cá nhân tôi muốn thay vì không phải phản ánh phân cấp lớp hình dạng nhưng tạo lớp loại cho "những thứ có diện tích bề mặt", "thứ có khối lượng", "những thứ có bán kính". ..

Điều này cho phép bạn viết các chức năng có các loại hình cụ thể, nói hình cầu, chỉ (vì mỗi hình dạng là kiểu riêng), nhưng bạn không thể viết hàm có "hình dạng bất kỳ" và sau đó phân biệt các loại hình bê tông khác nhau.

20

Bản dịch thẳng tiến.

type Vector3D = (Double, Double, Double) 

class Shape shape where 
    name :: shape -> String 
    position :: shape -> Vector3D 

data Sphere = Sphere { 
    sphereName :: String, 
    spherePosition :: Vector3D, 
    sphereRadius :: Double 
} 

data Prism = Prism { 
    prismName :: String, 
    prismPosition :: Vector3D, 
    prismDimensions :: Vector3D 
} 

instance Shape Sphere where 
    name = sphereName 
    position = spherePosition 

instance Shape Prism where 
    name = prismName 
    position = prismPosition 

Bạn thường không làm điều này; các danh sách lặp lại và đa hình yêu cầu các phần mở rộng ngôn ngữ.

Thay vào đó, dán chúng vào một kiểu dữ liệu đóng duy nhất có lẽ là giải pháp đầu tiên bạn nên sử dụng.

type Vector3D = (Double, Double, Double) 

data Shape 
    = Sphere { name :: String, position :: Vector3D, radius :: Double } 
    | Prism { name :: String, position :: Vector3D, dimensions :: Vector3D } 

Bạn chắc chắn có thể mô phỏng nhiều cấp độ thừa kế bằng cách tạo ra nhiều typeclasses:

class (Shape shape) => Prism shape where 
    dimensions :: Vector3D 
data RectangularPrism = ... 
data TriangularPrism = ... 
instance Prism RectangularPrism where ... 
instance Prism TriangularPrism where ... 

Bạn cũng có thể mô phỏng nó bằng cách nhúng kiểu dữ liệu.

type Vector3D = (Double, Double, Double) 

data Shape = Shape { name :: String, position :: Vector3D } 

data Sphere = Sphere { sphereToShape :: Shape, radius :: Double } 
newSphere :: Vector3D -> Double -> Shape 
newSphere = Sphere . Shape "Sphere" 

data Prism = Prism { prismToShape :: Shape, dimensions :: Vector3D } 

data RectangularPrism = RectangularPrism { rectangularPrismToPrism :: Prism } 
newRectangularPrism :: Vector3D -> Vector3D -> RectangularPrism 
newRectangularPrism = (.) RectangularPrism . Prism . Shape "RectangularPrism" 

data TriangularPrism = TriangularPrism { triangularPrismToPrism :: Prism } 
newTriangularPrism :: Vector3D -> Vector3D -> TriangularPrism 
newTriangularPrism = (.) TriangularPrism . Prism . Shape "TriangularPrism" 

Nhưng mô phỏng OO trong Haskell không ở bất kỳ đâu gần như thỏa mãn như thực tế suy nghĩ theo cách Haskellish. Bạn đang cố làm gì vậy?

(Cũng lưu ý rằng tất cả các giải pháp này chỉ cho phép upcasts, downcasting là không an toàn và không được phép.)

+2

làm cách nào bạn thể hiện chuỗi thừa kế có nhiều cấp độ sâu? ví dụ. 'shape' ->' Prism' -> 'RectangularPrism' – barkmadley

2

Một bản dịch đơn giản phá vỡ ra phần mà thay đổi nhưng tránh typeclasses:

type Vector3D = (Float,Float,Float) 
data Body = Prism Vector3D | Sphere Double 
radius (Prism position) = -- code here 
radius (Sphere r) = r 

sau đó

data Shape = Shape { 
    name :: String, 
    position :: Vector3D, 
    body :: Body 
} 

shapeOnly (Shape _ pos _) = -- code here 

both = radius . body 

sphereOnly (Shape _ _ (Sphere radius)) = -- code here 
sphereOnly _ = error "Not a sphere" 

Đây là không phải một câu hỏi thực sự dễ dàng. Thiết kế cấu trúc dữ liệu rất khác nhau giữa C++ và Haskell, vì vậy tôi đặt cược rằng hầu hết mọi người đến từ một ngôn ngữ OO đều yêu cầu điều tương tự. Thật không may, cách tốt nhất để học là làm; đặt cược tốt nhất của bạn là để thử nó trên cơ sở từng trường hợp cụ thể cho đến khi bạn tìm hiểu cách mọi thứ hoạt động trong Haskell.

Câu trả lời của tôi khá đơn giản, nhưng nó không giải quyết tốt với trường hợp một lớp con C++ có các phương thức mà những người khác không làm. Nó ném một lỗi thời gian chạy và yêu cầu thêm mã để khởi động. Bạn cũng phải quyết định xem mô-đun "phân lớp" có quyết định có ném lỗi hay mô-đun "siêu lớp" hay không.

6

Giống như Nathan nói, mô hình hóa kiểu dữ liệu hoàn toàn khác trong Haskell từ C++.Bạn có thể xem xét cách tiếp cận sau:

data Shape = Shape { name  :: String, position :: Vector3d } 
data Sphere = Sphere { sphereShape :: Shape, radius :: Float } 
data Prism = Prism { prismShape :: Shape, width :: Float, height :: Float, depth :: Float } 

Nói cách khác, mô hình tham chiếu đến các lớp siêu là trường bổ sung trong kiểu dữ liệu của bạn. Nó dễ dàng mở rộng đến các chuỗi kế thừa dài hơn.

Không sử dụng các loại lớp học, như đề xuất tạm thời. Chúng được sử dụng cho các hàm quá tải và đó không phải là vấn đề ở đây chút nào: câu hỏi của bạn liên quan đến mô hình hóa của dữ liệu, chứ không phải hành vi.

Hy vọng điều này sẽ hữu ích!

+0

Bạn có gợi ý rằng khi mô hình hóa kế thừa dữ liệu, hãy sử dụng các trường thừa, khi mô hình hóa thừa kế hành vi, sử dụng các lớp kiểu. Có thể nhập các lớp tham số trên các lớp loại khác không? – CMCDragonkai

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