Как мне переписать функцию Хаскелла из двух аргументов в свободный от точек стиль
У меня есть следующая функция в 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