Бесточечный в Haskell

У меня есть этот код, который я хочу сделать point-free;

(k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))

Как это сделать?

также есть некоторые общие правила для точечного свободного стиля, кроме "подумайте об этом amd, придумайте что-нибудь"?

5 ответов


чтобы включить функцию

func x y z = (some expression in x, y and z)

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

func x y z = (some function pipeline built using x and y) z

тогда я могу отменить zs, чтобы получить

func x y = (some function pipeline built using x and y)

затем повторение процесса для y и x должно закончиться func в свободной форме. Существенная трансформация для распознавания в этом процессе:

    f z = foo $ bar z    -- or f z = foo (bar z)
<=> f z = foo . bar $ z
<=> f   = foo . bar

важно помнить, что при частичной оценке вы можете "прервать" последний аргумент функции:

foo $ bar x y == foo . bar x $ y    -- foo applied to ((bar x) applied to y)

для конкретной функции, рассмотрим поток, который k и t пройти:

  1. применить ord каждому из них
  2. добавить результаты
  3. вычесть 2 * a
  4. возьмите результат mod 26
  5. добавить
  6. применить chr

так как первая попытка упрощения, мы получить:

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t

обратите внимание, что вы можете избежать flip С помощью раздела mod, и разделы, используя - запутаться в Haskell есть subtract функция (они сталкиваются с синтаксисом для написания отрицательных чисел:(-2) означает отрицательный 2, и это не то же самое, что subtract 2).

в этой функции, ord k + ord t является отличным кандидатом для использования Data.Function.on (ссылке). Этот полезный комбинатор позволяет нам заменить ord k + ord t С функцией, применяемой к k и t:

func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ((+) `on` ord) k t

мы сейчас очень близки к тому

func k t = (function pipeline) k t

и поэтому

func = (function pipeline)

к сожалению, Haskell немного запутан, когда дело доходит до составления двоичной функции с последовательностью унарных функций, но есть трюк (я посмотрю, смогу ли я найти для него хорошую ссылку), и мы в конечном итоге:

import Data.Function (on)

func = ((chr . (+a) . (`mod` 26) . subtract (2*a)) .) . ((+) `on` ord)

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

import Data.Function (on)

(.:) = (.).(.)

func = (chr . (+a) . (`mod` 26) . subtract (2*a)) .: ((+) `on` ord)

чтобы отполировать это еще немного, вы можете добавить некоторые вспомогательные функции, чтобы отделить преобразование буквы Int от шифр Цезаря арифметика. Например: letterToInt = subtract a . ord


также есть некоторые общие правила для точечного свободного стиля, кроме "подумайте об этом amd, придумайте что-нибудь"?

вы всегда можете обмануть и использовать инструмент "pl" из lambdabot (либо перейдя в #haskell на freenode, либо используя, например,ghci на кислоте). Для вашего кода pl дает:

((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord

что на самом деле не является улучшением, если вы спросите меня.


определенно есть набор трюков для преобразования выражения в стиль без точек. Я не претендую быть экспертом, но вот несколько советов.

во-первых, вы хотите изолировать аргументы функции в самом правом члене выражения. Ваши основные инструменты здесь будут flip и $, используя правила:

f a b ==> flip f b a
f (g a) ==> f $ g a

здесь f и g функции, и a и b выражения. Итак, для начала:

(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
-- replace parens with ($)
(\k t -> chr $ (a +) . flip mod 26 $ ord k + ord t - 2*a)
-- prefix and flip (-)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ord k + ord t)
-- prefix (+)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ (+) (ord k) (ord t))

теперь мы должны получить t на правой стороне. Для этого используйте правило:

f (g a) ==> (f . g) a

и так:

-- pull the t out on the rhs
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((+) (ord k) . ord) t)
-- flip (.) (using a section)
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) $ (+) (ord k)) t)
-- pull the k out
(\k t -> chr $ (a +) . flip mod 26 $ flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

теперь нам нужно повернуть все влево от k и t в один большой функциональный член, так что у нас есть выражение вида (\k t -> f k t). Вот здесь все становится немного умопомрачительным. Для начала отметим, что все условия до последнего $ функции с одним аргументом, поэтому мы можем составить они:

(\k t -> chr . (a +) . flip mod 26 . flip (-) (2*a) $ ((. ord) . ((+) . ord)) k t)

теперь у нас есть функция типа Char -> Char -> Int что мы хотим написать функцию типа Int -> Char, давая функцию типа Char -> Char -> Char. Мы можем достичь этого, используя (очень странное) правило

f (g a b) ==> ((f .) . g) a b

это дает нам:

(\k t -> (((chr . (a +) . flip mod 26 . flip (-) (2*a)) .) . ((. ord) . ((+) . ord))) k t)

теперь мы можем просто применить снижение бета:

((chr . (a +) . flip mod 26) .) . (flip flip (2*a) . ((-) . ) . ((. ord) . (+) .ord))

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

(\k t -> chr $ a + flip mod 26 (ord k + ord t - 2*a))

прежде всего,flip ненужно:

(\k t -> chr $ a + (ord k + ord t - 2*a) `mod` 26)

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

encode_characters k t = chr $ encode (ord k) (ord t)
encode x y = (x + y - 2*a) `mod` 26 + a

I также дал имя первому выражению, чтобы сделать его более ясным и многоразовым. encode_characters теперь легко сделать точку-бесплатно, используя технику из @Nefrubyr:

encode_characters = chr . encode `on` ord

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

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

А. реализовать и упростить функцию encode_n_characters :: [Char] -> Char здесь encode_characters k t = encode_n_characters [k, t]. Является ли результат проще специализированного двух аргумента функции?

Б. реализовать функцию encode' определен через encode' (x + y) = encode x y и повторно encode_characters С помощью этой функции. У функции становятся проще? Реализация проще в целом? Is encode' более или менее многоразовые, чем encode?


Connect on IRC, #haskell и задать lambdabot !:

<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]