Простые функции Haskell в стиле point-free

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

f x = 5 + 8/x, который я изменил как f x = (+) 5 $ (/) 8 x

Итак, я подумал, что это может быть примерно так:

f = (+) 5 $ (/) 8

но когда я запускаю это в ghci, я получаю это сообщение:

No instance for (Num (a0 -> a0))
  arising from the literal `5' at Test.hs:3:9
Possible fix: add an instance declaration for (Num (a0 -> a0))
In the first argument of `(+)', namely `5'
In the first argument of `($)', namely `(+) 5'
In the expression: (+) 5 $ (/) 8
Failed, modules loaded: none.

Я не понимаю сообщение "Нет экземпляра для...". Что мне нужно сделать, чтобы написать эту функцию в стиле point-free?

Ответ 1

$ имеет очень низкий приоритет. Итак, f x = (+) 5 $ (/) 8 x на самом деле означает f x = (+) 5 $ ((/) 8 x). Вместо этого перепишите это как

f x = (+) 5 ( (/) 8 x)
f x = ((+) 5) ( ((/) 8) x)
f x = ((+) 5) .  ( ((/) 8) ) x
f = ((+) 5) . ( (/) 8 )
f = (5+) . (8/)

Последнее выражение имеет смысл: f - это композиция из двух операций, сначала разделите 8 на то, что у вас есть, а затем добавьте 5 к результату. Помните, g.h означает "применить h, а затем применить g к результату этого".

Ответ 2

Программа "pointfree" может быть установлена ​​с помощью cabal install pointfree и показывает вам, как писать выражение в стиле pointfree. Например:

$ pointfree "f x = 5 + 8/x"
f = (5 +) . (8 /)

Объяснение этого преобразования:

  • Вы можете использовать "разделы" для функций infix/operator. (a +) == \b -> a + b и (+ a) == \b -> b + a
  • Функция . принимает результат второго параметра, который является функцией с одним аргументом и применяет его к первому аргументу.

Ответ 3

Преобразование из лямбда-исчисления (которое Haskell является вариантом) терминов SKI (абсолютно свободные функции, используя только const (K), id ( I) и <*> ( S)) можно выполнить со следующими простыми правилами:

  • \x -> x переводится на id;
  • \x -> y без x, встречающегося в y, переводится в const y;
  • \x -> f g переводится на f' <*> g' где
    • f' - это перевод \x -> f и
    • g' является переводом \x -> g.

Теперь вы можете задаться вопросом, куда входит .. Существует специальный случай последнего перевода: если f не имеет свободных вхождений x, то \x -> f g переводится на const f <*> (\x -> g), который равен f . (\x -> g).

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

f = \x -> ((+) 5) (((/) 8) x) = -- by the special-case (.) rule
((+) 5) . (\x -> (((/) 8) x)) = -- by eta-reduction ((\x -> f x) = f)
((+) 5) . ((/) 8)

Эта-редукция не нужна для завершения перевода, но без нее мы получим что-то более беспорядочное. Например, последний шаг привел бы вместо ((+) 5) . ((/) 8) . id.

Ответ 4

Ты был очень близок. Позвольте мне добавить еще один $ для иллюстрации:

f x = (+) 5 $ (/) 8 $ x

Должно быть ясно, что выражение (+) 5 - это функция, которая принимает один числовой ввод и производит числовой вывод. То же самое относится к выражению (/) 8. Таким образом, вы берете любое число, которое вводится, x и сначала применяете функцию (/) 8 ", а затем применяете функцию (+) 5".

Всякий раз, когда у вас есть цепочка функций, разделенных $, вы можете заменить все, кроме самого правого, на . Значение, если у вас есть a $ b $ c $ d, это эквивалентно a . b . c $ d.

f x = (+) 5 . (/) 8 $ x

На этом этапе вместо этого удалим $ и в скобках.

f x = ((+) 5 . (/) 8) x

Теперь должно быть ясно, что вы можете удалить трейлинг x с обеих сторон:

f = (+) 5 . (/) 8

Это основная идея. Если у вас f x = expr x, вы можете "eta уменьшить" его до f = expr. Чтобы создать код без кода, вам нужно просто узнать, как большая функция состоит из меньших функций. Частичное применение иногда необходимо для точечного свободного кода (как в этом случае, (+) 5 и (/) 8 частично применяются). Программа "pointfree" очень полезна, когда вы не хотите об этом думать; Lambdabot на канале #haskell irc использует эту программу в качестве плагина, поэтому вам даже не нужно устанавливать ее самостоятельно; просто спросите:

<DanBurton> @pl let f x = 5 + 8 / x in f
<lambdabot> (5 +) . (8 /)