Что делать, если я хочу изменить порядок аргументов в функции?
Существует flip
:
flip :: (a -> b -> c) -> b -> a -> c
но я не вижу, как заставить его работать для большего количества аргументов. Есть ли общий метод для перестановки аргументов?
Что делать, если я хочу изменить порядок аргументов в функции?
Существует flip
:
flip :: (a -> b -> c) -> b -> a -> c
но я не вижу, как заставить его работать для большего количества аргументов. Есть ли общий метод для перестановки аргументов?
Если вы чувствуете, что редактируете функции после их написания, вы действительно должны читать Коналл Эллиотт превосходные комментеры для текстовых редакторов блога.
http://conal.net/blog/posts/semantic-editor-combinators
На самом деле, все должны прочитать его в любом случае. Это действительно полезно
метод (который я злоупотребляю здесь). Conal использует больше конструкций, чем просто result
и flip
, для очень гибкого эффекта.
result :: (b -> b') -> ((a -> b) -> (a -> b'))
result = (.)
Предположим, что у меня есть функция, которая использует 3 аргумента
use3 :: Char -> Double -> Int -> String
use3 c d i = c: show (d^i)
и я бы хотел обменять первые два, я просто использовал бы flip use3
, как вы говорите,
но если бы я захотел поменять второй и третий, то я хочу применить flip
к результату применения use3
к его первому аргументу.
use3' :: Char -> Int -> Double -> String
use3' = (result) flip use3
Пусть перемещаются и заменяют четвертый и пятый аргументы функции use5
, которая использует 5.
use5 :: Char -> Double -> Int -> (Int,Char) -> String -> String
use5' :: Char -> Double -> Int -> String -> (Int,Char) -> String
use5 c d i (n,c') s = c : show (d ^ i) ++ replicate n c' ++ s
Нам нужно применить flip
к результату применения use5
к нему первые три аргумента,
так что результат результата результата:
use5' = (result.result.result) flip use5
Почему бы не сохранить мышление позже и определить
swap_1_2 :: (a1 -> a2 -> other) -> (a2 -> a1 -> other)
swap_2_3 :: (a1 -> a2 -> a3 -> other) -> (a1 -> a3 -> a2 -> other)
--skip a few type signatures and daydream about scrap-your-boilerplate and Template Haskell
swap_1_2 = flip
swap_2_3 = result flip
swap_3_4 = (result.result) flip
swap_4_5 = (result.result.result) flip
swap_5_6 = (result.result.result.result) flip
... и то, где вы должны остановиться, если вам нравится простота и элегантность.
Обратите внимание, что тип other
может быть b -> c -> d
, поэтому из-за сказочной карри и правой ассоциативности ->
,
swap_2_3 работает для функции, которая принимает любое количество аргументов выше двух.
Для чего-то более сложного, вы должны действительно написать перестановочную функцию вручную.
Далее следует только ради интеллектуального любопытства.
Теперь, как насчет замены второго и четвертого аргументов? [Кроме того, есть теорема, которую я помню из своих лекций по алгебре что любая перестановка может быть сделана в качестве композиции для переключения соседних элементов.]
Мы могли бы сделать это вот так:
Шаг 1: переместите 2 рядом с 4 (swap_2_3
)
a1 -> a2 -> a3 -> a4 -> otherstuff
a1 -> a3 -> a2 -> a4 -> otherstuff
замените их там, используя swap_3_4
a1 -> a3 -> a2 -> a4 -> otherstuff
a1 -> a3 -> a4 -> a2 -> otherstuff
затем поменяйте 4 назад на позицию 2 с помощью swap_2_3
снова:
a1 -> a3 -> a4 -> a2 -> otherstuff
a1 -> a4 -> a3 -> a2 -> otherstuff
так
swap_2_4 = swap_2_3.swap_3_4.swap_2_3
Может быть, есть более тонкий способ попасть туда с большим количеством результатов и переворачивает, но случайный беспорядок не нашел его для меня!
Аналогично, чтобы поменять 1 и 5, мы можем переместить 1 на 4, поменять местами с 5, переместить 5 назад с 4 на 1.
swap_1_5 = swap_1_2.swap_2_3.swap_3_4 . swap_4_5 . swap_3_4.swap_2_3.swap_1_2
Или, если вы предпочитаете, вы можете повторно использовать swap_2_4
, щелкнув по концам
(обмен 1 с 2 и 5 с 4), swap_2_4, затем снова перевернутый на концах.
swap_1_5' = swap_1_2.swap_4_5. swap_2_4 .swap_4_5.swap_1_2
Конечно, гораздо проще определить
swap_1_5'' f a b c d e = f e b c d a
который имеет преимущество быть ясным, consise, эффективным и имеет полезную подпись типа в ghci без явного аннотирования его.
Однако это был фантастически интересный вопрос, спасибо.
Лучший способ в целом - просто сделать это вручную. Предположим, что у вас есть функция
f :: Arg1 -> Arg2 -> Arg3 -> Arg4 -> Res
и вы хотели бы
g :: Arg4 -> Arg1 -> Arg3 -> Arg2 -> Res
тогда вы пишете
g x4 x1 x3 x2 = f x1 x2 x3 x4
Если вам нужна определенная перестановка несколько раз, вы можете, конечно, отвлечься от нее, например, flip
для случая с двумя аргументами:
myflip :: (a4 -> a1 -> a3 -> a2 -> r) -> a1 -> a2 -> a3 -> a4 -> r
myflip f x4 x1 x3 x2 = f x1 x2 x3 x4