Как мне переписать функцию Хаскелла из двух аргументов в свободный от точек стиль


У меня есть следующая функция в 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 21

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