Как изменить порядок аргументов?
Что если я захочу изменить порядок аргументов в функции?
Есть flip
:
flip :: (a -> b -> c) -> b -> a -> c
но я не вижу, как заставить это работать для большего количества аргументов. Есть ли общий метод для перестановки аргументов?
2 ответа
Если вам хочется редактировать функции после того, как они написаны, вам действительно стоит прочесть превосходные комбинаторы семантических редакторов в блоге Конала Эллиота.
http://conal.net/blog/posts/semantic-editor-combinators
На самом деле, все должны это прочитать. Это действительно полезный метод (которым я здесь злоупотребляю). Конал использует больше конструкций, чем просто 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
который имеет преимущество в том, что он понятен, сжат, эффективен и имеет полезную сигнатуру типа в 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