Я пытаюсь использовать в списке разные типы данных. например:

data Shape = Square Int
            | Circle Int
            | Rectangle Int Int
            | Triangle Int Int Int
                 deriving (Show)


shapes = [Square 5, Circle 2, Rectangle 10 5]
showShapes :: [Shape] -> [Int]
showShapes [] = []
showShapes (s:xs) = getArea (s : xs)

Однако я изо всех сил пытаюсь создать метод getArea, так как мне нужен один для каждого типа. Я не знаю, как это сделать с помощью сопоставления шаблонов параметров. Есть ли способ сделать это или я неправильно решаю эту проблему?

Редактировать

Как бы вы это сделали, используя оператор if и функцию typeOf

Я попытался изменить форму на это:

data Shape = Square Int
            | Rectangle Int Int
            | Triangle Int Int Int
                 deriving (Show, Typeable)

Но я получаю ошибку времени компиляции!

0
Yahya Uddin 11 Ноя 2014 в 04:48
3
"Но я получаю ошибку времени компиляции!" - Что это за ошибка? Довольно сложно диагностировать проблему, не зная, что не так!
 – 
bheklilr
11 Ноя 2014 в 05:19
3
Здесь вы не помещаете разные типы данных в список. У вас есть только один тип данных: Shape. Форма имеет 4 разных случая, и сопоставление шаблонов параметров — это именно то, как вы определяете, какой случай у вас есть. Использование typeOf здесь не имеет смысла.
 – 
Ben
11 Ноя 2014 в 11:17
О да, это имеет смысл. Огромное спасибо
 – 
Yahya Uddin
11 Ноя 2014 в 17:11

2 ответа

Лучший ответ

В вашем простом случае просто используйте сопоставление с образцом в getArea, но вам придется преобразовать свои значения в двойные, поскольку площадь круга никогда не будет целым числом, если у вас есть целочисленный радиус:

getArea :: Shape -> Double
getArea (Square l)       = fromIntegral $ l * l
getArea (Circle r)       = pi * fromIntegral r ^ 2
getArea (Rectangle l w)  = fromIntegral $ l * w
-- assuming the constructor takes the 3 side lengths
getArea (Triangle a b c) = sqrt $ p * (p - a') * (p - b') * (p - c')
    where
        [a', b', c'] = map fromIntegral [a, b, c]
        p = (a' + b' + c') / 2

Хотя я не знаю, чем вы хотите заниматься в showShapes. Обычно слово show в Haskell означает то же самое, что и toString в других языках, но вы пытаетесь применить getArea внутри него. Несмотря на это, сопоставление с шаблоном для showShapes отключено, вам нужны скобки вокруг s:xs, иначе вы получите синтаксическую ошибку, и вы не сможете добавить число перед списком Shape как с getArea s : xs. Вместо этого вы, возможно, захотите рассчитать площадь для каждой фигуры в списке? Для этого вы можете использовать map:

getAreas :: [Shape] -> [Double]
getAreas shapes = map getArea shapes
4
bheklilr 11 Ноя 2014 в 18:35
Огромное спасибо. Просто расширение исходного вопроса, как бы вы это сделали, используя оператор if и функцию «typeOf». Функция typeOf shape не работала.
 – 
Yahya Uddin
11 Ноя 2014 в 05:02
1
Каков тип typeOf? Похоже, это тоже домашнее задание, что вы пробовали? Пожалуйста, имейте в виду, что SO не является форумом для того, чтобы заставить других делать вашу домашнюю работу за вас.
 – 
bheklilr
11 Ноя 2014 в 05:05
Это не часть моей домашней работы, лол. Я отредактирую свой пост, чтобы показать, что я пробовал.
 – 
Yahya Uddin
11 Ноя 2014 в 05:09
Осторожно: в getArea (Circle r) = pi * fromIntegral r есть опечатка.
 – 
gallais
11 Ноя 2014 в 17:59
1
Исправлено, вот что происходит, когда я пишу быстрый ответ.
 – 
bheklilr
11 Ноя 2014 в 18:36

Обратите внимание, что в этом случае вам не нужно хранить все цифры в одном типе данных. Вместо этого вы можете использовать экзистенциальную количественную оценку:

{-# LANGUAGE ExistentialQuantification #-}

data Square    = Square Int
data Circle    = Circle Int
data Rectangle = Rectangle Int Int

class HasArea a where
    area :: a -> Double

instance HasArea Square where
    area (Square n) = fromIntegral n * fromIntegral n

instance HasArea Circle where
    area (Circle r) = pi * fromIntegral r ^ 2

instance HasArea Rectangle where
    area (Rectangle n m) = fromIntegral n * fromIntegral m

data Shape = forall s. HasArea s => Shape s

shapes :: [Shape]
shapes = [Shape (Square 5), Shape (Circle 2), Shape (Rectangle 10 5)]

shapeArea :: Shape -> Double
shapeArea (Shape s) = area s

main = print $ map shapeArea shapes

Вы можете прочитать об экзистенциальной количественной оценке здесь: http://en.wikibooks.org/wiki/Haskell/Existenfully_quantified_types

Сама по себе экзистенциальная количественная оценка слабее, чем обобщенные алгебраические типы данных. Вы можете прочитать о них здесь: http://en.wikibooks.org/wiki/Haskell/GADT < / а>

1
effectfully 11 Ноя 2014 в 19:01
Я бы сказал, что единственным реальным использованием экзистенциалов здесь было бы то, что вы можете отображать shapeArea по списку вместо того, чтобы вызывать shapeArea для каждого отдельного элемента списка. Это может быть полезно для уменьшения количества типизации и избыточности в зависимости от ситуации, но на самом деле это только для создания промежуточной структуры, которая никогда не используется, а только результат отображения функции на нее. Если бы у меня было 50 фигур в списке, это сделало бы мою жизнь намного проще, а мой код значительно понятнее, но это было бы не более полезно, чем [Double].
 – 
bheklilr
11 Ноя 2014 в 17:17
@чи, я не согласен. Во-первых, автор процитированного вами поста говорит, что «на самом деле это просто разница в обозначениях». Различие в обозначениях не является антипаттерном. Во-вторых, toWidget :: Window -> Widget на самом деле windowToWidget :: Window -> Widget. radio_buttonToWidget, combo_boxToWidget... вместо одного Widget. В-третьих, в более сложных случаях вам все равно понадобится запись вместо безымянного списка двойников. Почему вы должны предпочесть что-то с предварительно вычисленными (семантически предварительно вычисленными из-за лени) значениями? Очевидно, что вы можете снабдить свою запись «методами», но зачем, когда у вас есть эквалайзер?
 – 
effectfully
11 Ноя 2014 в 20:42
Честно говоря, я думаю, что «антипаттерн» имеет некоторую ценность в некоторых сложных сценариях: в сообщении Люка Палмера даже показан случай, когда вы получаете меньше информации о типах из предложенного простого представления, чем вы получили бы с экзистенциальными типами и классами типов ( IIRC, перемещение виджета, который сохраняет тип с помощью TC, а не иначе). В таких случаях я не согласен с тем, чтобы называть инопланетян «антипаттерном». Однако в этом конкретном примере Shape/area экзистенциальные типы кажутся излишними. Я понимаю, что это вопрос личных предпочтений в конце концов.
 – 
chi
11 Ноя 2014 в 21:28
@chi, я согласен, что в этом примере это излишне. Но это только пример.
 – 
effectfully
11 Ноя 2014 в 22:02