Бесплатный в Хаскелле
-
20-09-2019 - |
Вопрос
У меня есть этот код, который я хочу сделать без точек;
(\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
Как я могу это сделать?
Также есть ли некоторые общие правила для свободного стиля, кроме «Подумайте об этом, что -то придумывает»?
Решение
Чтобы повернуть функцию
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
- Возьмите результат, мод 26
- Добавить
- Подать заявление
chr
Итак, в качестве первой попытки упростить, мы получаем:
func k t = chr . (+a) . (`mod` 26) . subtract (2*a) $ ord k + ord t
Обратите внимание, что вы можете избежать flip
Используя раздел на mod
, и разделы с использованием -
Получите грязную в Хаскелле, так что есть 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)
К сожалению, Хаскелл немного грязный, когда дело доходит до составления бинарной функции с последовательности уникальных функций, но есть трюк (я посмотрю, смогу ли я найти для нее хорошую ссылку), и мы получим:
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
Другие советы
Также есть ли некоторые общие правила для свободного стиля, кроме «Подумайте об этом, что -то придумывает»?
Вы всегда можете обмануть и использовать инструмент «PL» от Lambdabot (либо отправившись в #haskell на Freenode, либо с помощью EG 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
Я также дал имя первому выражению, чтобы сделать его яснее и повторно используемого. encode_characters
Теперь легко сделать точку свободным, используя технику от @nefrubyr:
encode_characters = chr . encode `on` ord
Что касается второго выражения, я не могу создать форму, которая более читаемой, чем любая в других ответах, и все они менее читаемы, чем точечная форма. Поэтому я бы посоветовал прекратить рефакторинг на этом этапе и восхищаться чистотой и повторным использованием полученного кода.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PS: в качестве упражнения, в зависимости от контекста проблемы, некоторая небольшая модификация функциональных интерфейсов (какие данные в какой форме передаются в функции) может дать более упрощения, обобщая проблему.
A. Реализация и упрощение функции encode_n_characters :: [Char] -> Char
куда encode_characters k t = encode_n_characters [k, t]
. Анкет Является ли результат проще, чем специализированная функция с двумя аргументами?
B. Реализация функции encode'
определяется через encode' (x + y) = encode x y
и переопределение encode_characters
используя эту функцию. Любая из функций становится проще? Реализация в целом проще? Является encode'
более или менее повторно используется, чем encode
?
Подключиться на IRC, #haskell, а также Спросите Lambdabot!:
<you> @pl (\k t -> chr $ a + flip mod 26 (ord k + ord t -2*a))
<lambdabot> [the answer]