Бесточечный в 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
тогда я могу отменить z
s, чтобы получить
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
пройти:
- применить
ord
каждому из них - добавить результаты
- вычесть 2 * a
- возьмите результат mod 26
- добавить
- применить
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]