2015-11-01 6 views
2

Извинения за мои плохие формулировки вопроса. Я попытался найти ответ, но не зная, что искать, очень сложно найти его.Можно ли обобщить уравнения в Haskell?

Вот простая функция, которая вычисляет площадь треугольника.

triangleArea  :: Float -> Float -> Float -> Float 
triangleArea a b c 
    | (a + b) <= c = error "Not a triangle!" 
    | (a + c) <= b = error "Not a triangle!" 
    | (b + c) <= a = error "Not a triangle!" 
    | otherwise  = sqrt (s * (s - a) * (s - b) * (s - c)) 
     where s  = (a + b + c)/2 

Три строки функции были использованы для проверки ошибок. Мне было интересно, можно ли сконденсировать эти три линии в одну общую линию.

мне было интересно, если что-то похожее на следующее будет возможно

(arg1 + arg2) == arg3 

где Haskell знает, чтобы проверить каждую возможную комбинацию из трех аргументов.

+2

Ну, вы можете написать 'any [arg1 + arg2 <= arg3 | [arg1, arg2, arg3] <- перестановки [a, b, c]] ', но на самом деле это больше работает, чем ваш исходный код (он не предполагает' + 'коммутирует). – melpomene

+7

ваши 3 условия эквивалентны 'a + b + c <= 2 * максимум [a, b, c]' –

+0

@ behzad.nouri Вы можете отправить ответ с этим. – chi

ответ

5

Я думаю, комментарий @ behzad.nouri является лучшим. Иногда делать небольшую математику - лучший способ программирования. Вот несколько преувеличенное расширение на решение @ melpomene, которое, как мне казалось, было бы интересно поделиться. Давайте напишем функцию, аналогичную permutations но вычисляет комбинации:

import Control.Arrow (first, second) 

-- choose n xs returns a list of tuples, the first component of each having 
-- n elements and the second component having the rest, in all combinations 
-- (ignoring order within the lists). N.B. this would be faster if implemented 
-- using a DList. 
choose :: Int -> [a] -> [([a],[a])] 
choose 0 xs = [([], xs)] 
choose _ [] = [] 
choose n (x:xs) = 
    map (first (x:)) (choose (n-1) xs) ++ 
    map (second (x:)) (choose n xs) 

Так ..

ghci> choose 2 [1,2,3] 
[([1,2],[3]),([1,3],[2]),([2,3],[1])] 

Теперь вы можете написать

triangleArea a b c 
    | or [ x + y <= z | ([x,y], [z]) <- choose 2 [a,b,c] ] = error ... 
2

Вот две идеи.

  1. Используя существующие инструменты, вы можете сгенерировать все перестановки аргументов и проверить, что все они удовлетворяют условию. Таким образом:

    import Data.List 
    triangleArea a b c 
        | any (\[x, y, z] -> x + y <= z) (permutations [a,b,c]) 
           = error "Not a triangle!" 
        | otherwise = {- ... -} 
    

    Это не требует особого написания кода; однако он будет искать некоторые перестановки, которые вам не нужны.

  2. Используйте обычный трюк для выбора элемента из списка и слева. zippers функция один я использую часто:

    zippers :: [a] -> [([a], a, [a])] 
    zippers = go [] where 
        go b [] = [] 
        go b (v:e) = (b, v, e) : go (v:b) e 
    

    Мы можем использовать его, чтобы построить функцию, которая выбирает только соответствующие троек элементов:

    triples :: [a] -> [(a, a, a)] 
    triples xs = do 
        (b1, v1, e1) <- zippers xs 
        (b2, v2, e2) <- zippers e1 
        v3 <- b1 ++ b2 ++ e2 
        return (v1, v2, v3) 
    

    Теперь мы можем написать нашу охрану, как в части (1), но он будет рассматривать только уникальные пары для добавления.

    triangleArea a b c 
        | any (\(x, y, z) -> x + y <= z) (triples [a,b,c]) 
           = error "Not a triangle!" 
        | otherwise = {- ... -} 
    
3

Это не решает вопрос о том, как сократить ваши проверки ошибок кода, но вы можете быть в состоянии ограничить, как часто вы повторить его, определив некоторые новые типы с инвариантами. Эта функция нуждается в проверке ошибок, потому что вы не можете доверять пользователю, чтобы поставить Float троек, которые делают разумный треугольник, и если вы продолжаете определять функции таким образом, то каждая связанная с треугольником функция, которую вы пишете, потребует аналогичных проверок ошибок.

Однако, если определить тип треугольника, вы можете проверить инварианты только один раз, когда создается треугольник, а затем все остальные функции будут гарантированно получить действительные треугольники:

module Triangle (Triangle(), mkTriangle, area) where 

data Triangle a = Triangle a a a deriving Show 

mkTriangle :: (Num a, Ord a) => a -> a -> a -> Either String (Triangle a) 
mkTriangle a b c 
    | a + b <= c = wrong 
    | a + c <= b = wrong 
    | b + c <= a = wrong 
    | otherwise = Right $ Triangle a b c 
    where wrong = Left "Not a triangle!" 

area :: Floating a => Triangle a -> a 
area (Triangle a b c) = sqrt (s * (s - a) * (s - b) * (s - c)) 
    where s = (a + b + c)/2 

Здесь мы экспортируем тип треугольника, но не его конструктор, так что клиент должен вместо этого использовать mkTriangle, который может выполнить необходимую проверку ошибок. Затем area и любые другие функции треугольника, которые вы пишете, могут опустить проверки, что они получают действительный треугольник. Этот общий шаблон называется «умные конструкторы».

Смежные вопросы