Haskell слишком много, где клаузулы, любые альтернативные предложения

Я совершенно новый в Haskell и при написании небольших программ, у меня обычно заканчивается слишком много предложений о том, чтобы проверить многие вещи в функции, так что хорошая практика - писать где предложения или есть ли другие хорошие альтернативы для этого?

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

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
       where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
             where
             checkRow []     = [] 
             checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

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

Спасибо

Ответ 1

Попробуйте написать абстрактные, многоразовые функции Вы сможете составить их намного проще

isUnique :: Eq a => [a] -> Bool
isUnique [] = True
isUnique (x:xs) = all (/= x) xs && isUnique xs

noDups :: Eq a => [[a]] -> Bool
noDups = all isUnique

Ответ 2

Вам не нужно предложение second where. вы можете поместить несколько функций в одно и то же предложение where. Все имена функций в одном и том же предложении where находятся в области тела этих функций. Подумайте о том, как работают функции верхнего уровня. Поэтому вы можете написать:

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
   where checkDup []     = []
         checkDup (x:xs) = checkRow x ++ checkDup xs

         checkRow []     = [] 
         checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

На самом деле это намного яснее, потому что в вашей версии, когда вы привязываете x в checkDup, что x все еще находится в области видимости во втором предложении where, но вы связываете аргументы checkRow с тем же именем. Я думаю, что это, вероятно, заставит GHC жаловаться, и это, безусловно, запутывает.

Ответ 3

Оставив в стороне некоторые детали вашего конкретного примера (имена не очень хорошо выбраны), я большой поклонник предложений:

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

  • Функция, определенная в предложении where, может захватывать параметры закрывающей функции, что часто облегчает чтение

В вашем конкретном примере вам не нужно вставлять предложения where - одно предложение where будет делать, потому что функции, определенные в одном и том же предложении where, взаимно рекурсивно связаны друг с другом. Есть другие вещи о коде, который может быть улучшен, но с единственным предложением where мне нравится мелкомасштабная структура.

N.B. Нет необходимости отступать над предложениями where так же глубоко, как и вы.

Ответ 4

noDups :: [[a]] -> Bool
noDups = and . checkDup
  where
    --checkDup
    checkDup []     = []
    checkDup (x:xs) = checkRow x ++ checkDup xs
    --alternatively
    checkDup xs = concat $ map checkRow xs
    --alternatively
    checkDup = concat . map checkRow
    --alternatively
    checkDup = concatMap checkRow
    --checkRow
    checkRow []     = [] 
    checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

Ответ 5

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

has_nested_duplicate :: (Eq a) => [[a]] -> Bool
has_nested_duplicate = any has_duplicate
  where
    has_duplicate []     = False
    has_duplicate (x:xs) = x `elem` xs || has_duplicate xs

Это использует сопоставление шаблонов, any, elem и (||). Чтобы получить отрицание, используйте not:

noDups :: (Eq a) => [[a]] -> Bool
noDups = not . has_nested_duplicate

Ответ 6

Haskell позволяет ссылаться на вещи, определенные в предложении where из предложения where (то же самое, что и привязка let). Фактически предложение where - это просто синтаксический сахар для связывания let, который позволяет нескольким определениям и взаимным ссылкам между прочим.

пример имеет порядок.

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
           where
               checkRow []     = [] 
               checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

становится

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
        checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

становится

noDups :: [[a]] -> Bool
noDups du = 
    let checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

    in  and (checkDup du)

Ответ 7

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

Я думаю, вы можете сделать четкое различие. Некоторые функции являются основными для модуля, они вносят непосредственный вклад в api. Существуют также функции, которые, когда они появляются в документации по модулю, оставляют читателя недоумевающим, что эта конкретная функция имеет отношение к цели модуля. Это явно вспомогательная функция.

Я бы сказал, что такая вспомогательная функция должна быть изначально подчиненной вызывающей функции. Если эта вспомогательная функция должна быть повторно использована внутри модуля, отделите эту вспомогательную функцию от вызывающей функции, сделав ее непосредственно доступной функцией модуля. Вероятно, вы не будете экспортировать эту функцию в определение модуля.
Позвольте называть это рефакторингом в стиле FP.

Жаль, что для функционального программирования не существует "полной версии кода". Я думаю, что причина в том, что слишком мало отраслевой практики. Но давайте собираем мудрость в stackoverflow: D