Непосредственно в Haskell
У меня есть этот код, который я хочу сделать бессмысленным;
(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
Как это сделать?
Также существуют некоторые общие правила для точечного стиля, отличного от "подумайте об этом, и придумайте что-нибудь"?
Ответы
Ответ 1
Чтобы включить функцию
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
, чтобы получить
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 от арифметики
Ответ 2
Также существуют некоторые общие правила для точечного стиля, отличного от "подумайте об этом, и придумайте что-нибудь"?
Вы всегда можете обмануть и использовать инструмент "pl" из lambdabot (либо перейдя на #haskell на freenode, либо используя, например, ghci on acid). Для вашего кода pl дает:
((chr . (a +) . flip mod 26) .) . flip flip (2 * a) . ((-) .) . (. ord) . (+) . ord
На самом деле это не улучшение, если вы спросите меня.
Ответ 3
Существует определенно набор трюков для преобразования выражения в стиль без точек. Я не претендую на роль эксперта, но вот несколько советов.
Во-первых, вы хотите изолировать аргументы функции в самом правом выражении выражения. Ваши основные инструменты здесь будут 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))
Ответ 4
Я предполагаю, что точка вашего освобождения точки - сделать код более кратким и читаемым. Поэтому я считаю, что целесообразно также сделать некоторые другие рефакторинги для упрощения, которые затем могут облегчить удаление переменных.
(\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
Я также дал имя первому выражению, чтобы сделать его более понятным и многоразовым. 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
, используя эту функцию. Упрощается ли какая-либо из функций? Является ли реализация более простой в целом? Является encode'
более или менее многоразовым, чем encode
?
Ответ 5
Подключитесь к IRC, #haskell и спросить lambdabot!:
<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]