Как мне переписать функцию Хаскелла из двух аргументов в свободный от точек стиль
У меня есть следующая функция в Haskell
agreeLen :: (Eq a) => [a] -> [a] -> Int
agreeLen x y = length $ takeWhile ((a,b) -> a == b) (zip x y)
Я пытаюсь научиться писать "идиоматические" Haskell, которые, похоже, предпочитают использовать . и $ вместо скобок, а также предпочитают безынерционный код, где это возможно. Я просто не могу избавиться от упоминания x и y явно. Есть идеи?
Я думаю, что у меня была бы та же проблема с pointfreeing любой функции двух аргументов.
Кстати, это просто в погоне за написанием хорошего кода; а не какой-то " use whatever это нужно, чтобы сделать его бессмысленным "домашним заданием".
Спасибо.
(добавлен комментарий) Спасибо за ответы. Вы убедили меня, что эта функция не выигрывает от pointfree. И вы также дали мне несколько замечательных примеров для практики трансформации выражений. Это все еще трудно для меня, и они, кажется, так же важны для Хаскелла, как указатели для С.
4 ответа:
И также предпочитают код pointfree, где это возможно.
Не "там, где это возможно", а "там, где это улучшает читаемость (или имеет другие очевидные преимущества)".
Чтобы освободить ваши
agreeLen x y = length $ takeWhile (\(a,b) -> a == b) (zip x y)Первым шагом было бы переместить
($)вправо и заменить тот, который у вас есть, на(.):agreeLen x y = length . takeWhile (\(a,b) -> a == b) $ zip x yТеперь вы можете переместить его еще дальше вправо:
agreeLen x y = length . takeWhile (uncurry (==)) . zip x $ yИ там вы можете сразу же отрубить один аргумент,
agreeLen x = length . takeWhile (uncurry (==)) . zip xТогда вы можете переписать что в качестве префиксного применения оператора композиции,
agreeLen x = (.) (length . takeWhile (uncurry (==))) (zip x)И вы можете написать
F (g x)
Как
f . g $ xВообще, здесь с
f = (.) (length . takeWhile (uncurry (==)))И
g = zip, даваяagreeLen x = ((.) (length . takeWhile (uncurry (==)))) . zip $ x, из которого легко удаляется аргумент
x. Затем вы можете преобразовать префиксное приложение(.)в раздел и получитьagreeLen = ((length . takeWhile (uncurry (==))) .) . zipНо это менее читаемо, чем оригинал, поэтому я не рекомендую делать это, за исключением практики преобразование выражений в бесточечный стиль.
Вы также можете использовать:
agreeLen :: (Eq a) => [a] -> [a] -> Int agreeLen x y = length $ takeWhile id $ zipWith (==) x yИдиоматический Хаскелл-это то, что легче читать, не обязательно то, что наиболее свободно от точки.
Еще одно краткое, беспунктовое решение:
agreeLen = ((length . takeWhile id) .) . zipWith (==)Эквивалентно:
agreeLen = (.) (length . takeWhile id) . zipWith (==)
Как указано в превосходном ответе Даниэля, ваша задача состоит в том, чтобы составить
fиg, Когдаfкак один аргумент иgдва. это может быть записаноf ??? gс правильным оператором (и с сигнатурой типа(c -> d) -> (a -> b -> c) -> a -> b -> d. Это соответствует оператору(.).(.)(см. там), который иногда определяется как.:. В этом случае ваше выражение становитсяlength . takeWhile (uncurry (==)) .: zipЕсли вы привыкли к оператору
.:, то эта бесплатная версия точки отлично читается. Я также могу вместо этого использовать(<$$$>) = fmap fmap fmapи получаемlength . takeWhile (uncurry (==)) <$$$> zip