@alanw123: p4pr близко к тому, что я ищу, но он не пересекает границы ответвления:
last if $type eq 'branch';
, Который был основной проблемой, которую я имел, когда я пытался писать свою собственную утилиту - Вы не можете (легко) сказать, как строки отображаются назад на файл, который перешелся от.
Попробуйте написать абстрактные многоразовые функции Вы сможете составить их намного проще
isUnique :: Eq a => [a] -> Bool
isUnique [] = True
isUnique (x:xs) = all (/= x) xs && isUnique xs
noDups :: Eq a => [[a]] -> Bool
noDups = all isUnique
Второе предложение 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
все еще находится в область видимости в предложении second where
, но вы привязываете аргументы checkRow
к тому же имени. Я думаю, что это, вероятно, заставит GHC жаловаться, и это определенно сбивает с толку.
Leaving aside some of the details of your particular example (the names are not expecially well chosen), I'm a big fan of where clauses:
A function defined in a where
clause can be better than a top-level function because a reader knows that the scope of the function is limited---it can be used in just a few places.
A function defined in a where
clause can capture parameters of an enclosing function, which often makes it easier to read
In your particular example, you don't need to nest the where
clauses---a single where
clause will do, because functions defined in the same where
clause are all mutually recursive with one another. There are other things about the code that could be improved, but with the single where
clause I like the large-scale structure fine.
N.B. It is not necessary to indent the where
clauses as deeply as you are doing.
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)
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
Хотя есть исключения, в общем случае вы можете определить «положительные» функции, т.е. в этом случае определить функцию, которая возвращает 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
и (||)
. Чтобы получить отрицание, используйте , а не
:
noDups :: (Eq a) => [[a]] -> Bool
noDups = not . has_nested_duplicate
Я чувствую то же, что и Норман , относительно поддержания чистоты глобального осциллографа. Чем больше функций вы предоставляете внутри вашего модуля, тем более неуклюжим становится это пространство имен. С другой стороны, наличие функции в глобальной области видимости вашего модуля делает его многоразовым.
Я думаю, вы можете провести четкое различие. Некоторые функции являются основными для модуля, они вносят вклад непосредственно в api. Есть также функции, которые, когда они появляются в документации модуля, заставляют читателя задаться вопросом, какое отношение эта конкретная функция имеет к назначению модуля. Очевидно, что это вспомогательная функция.
Я бы сказал, что такая вспомогательная функция должна быть подчиненной вызывающей функции. Если эта вспомогательная функция должна быть повторно использована в модуле, отделите эту вспомогательную функцию от вызывающей функции, сделав ее прямой доступной функцией модуля. Однако вы, скорее всего, не будете экспортировать эту функцию в определение модуля.
Назовем это рефакторингом в стиле FP.
Жалко, что не существует книги по функциональному программированию, подобной «полному коду». Я думаю, причина в том, что в отрасли слишком мало практики. Но давайте соберем мудрость о stackoverflow: D