Бессмысленно в Хаскеле - PullRequest
       12

Бессмысленно в Хаскеле

17 голосов
/ 17 марта 2010

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

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

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

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

Ответы [ 5 ]

52 голосов
/ 17 марта 2010

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

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:

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

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

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

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

В этой функции ord k + ord t является отличным кандидатом для использования Data.Function.on ( link ). Этот полезный комбинатор позволяет нам заменить 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

10 голосов
/ 17 марта 2010

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

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

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

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

3 голосов
/ 10 апреля 2012

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

(\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?

3 голосов
/ 17 марта 2010

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

Во-первых, вы хотите выделить аргументы функции в самом правом члене выражения. Ваши основные инструменты здесь будут 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))
0 голосов
/ 17 марта 2010

Подключите IRC, # haskell и ask lambdabot! :

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