Приложение функции Haskell и currying

Я всегда заинтересован в изучении новых языков, что держит меня на ногах и делает меня (я считаю) лучшим программистом. Мои попытки захватить Хаскелла приходят и уходят - дважды до сих пор - и я решил, что пришло время попробовать еще раз. В третий раз это очарование, верно?

Неа. Я перечитываю свои старые заметки... и разочаровываюсь: - (

Проблема, которая заставила меня потерять веру в прошлый раз, была простой: перестановки целых чисел. т.е. из списка целых чисел, в список списков - список их перестановок:

[int] -> [[int]]

Это на самом деле общая проблема, поэтому замена "int" выше на "a" все равно будет применяться.

Из моих заметок:

Я сначала его определяю самостоятельно, мне это удается. Ура!

Я посылаю свое решение своему хорошему другу - гуру Хаскелла, он обычно помогает учиться у гуру - и он посылает мне это, о чем я говорю, "выражает истинную силу языка, использование общих средств для кодирования ваших потребностей". Все для этого, я недавно выпил kool-помощь, отпустил:

permute :: [a] -> [[a]]
permute = foldr (concatMap.ins) [[]]
   where ins x []     = [[x]]
         ins x (y:ys) = (x:y:ys):[ y:res | res <- ins x ys]

Хм. Пусть это сломается:

bash$ cat b.hs
ins x []     = [[x]]
ins x (y:ys) = (x:y:ys):[ y:res | res <- ins x ys]

bash$ ghci
Prelude> :load b.hs
[1 of 1] Compiling Main             ( b.hs, interpreted )
Ok, modules loaded: Main.

*Main> ins 1 [2,3]
[[1,2,3],[2,1,3],[2,3,1]]

Хорошо, до сих пор, так хорошо. Потребовал мне минуту, чтобы понять вторую строчку "ins", но ОК: Он помещает 1-й аргумент во все возможные позиции в списке. Круто.

Теперь, чтобы понять foldr и concatMap. в "реальном мире Haskell", объяснили DOT...

(f . g) x

... как еще один синтаксис для...

f (g x) 

И в коде, отправленном гуру, DOT использовался из foldr, а функция "ins" как сгиб "сворачивается":

*Main> let g=concatMap . ins
*Main> g 1 [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

ОК, так как я хочу понять, как DOT используется гуру, я пробую эквивалентное выражение в соответствии с определением DOT (f. g) x = f (g x)...

*Main> concatMap (ins 1 [[2,3]])

<interactive>:1:11:
     Couldn't match expected type `a -> [b]'
            against inferred type `[[[t]]]'
     In the first argument of `concatMap', namely `(ins 1 [[2, 3]])'
     In the expression: concatMap (ins 1 [[2, 3]])
     In the definition of `it': it = concatMap (ins 1 [[2, 3]])

Что!?! Зачем? Хорошо, я проверяю подпись concatMap и обнаруживаю, что ей нужна лямбда и список, но это просто человеческое мышление; как GHC справляется? Согласно определению DOT выше...

(f.g)x = f(g x), 

... то, что я сделал, было действительным, заменяющим:

(concatMap . ins) x y = concatMap (ins x y)

Царапиновая головка...

*Main> concatMap (ins 1) [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

Итак... Объяснение DOT было очевидно слишком упрощен... DOT должен быть как-то достаточно умен, чтобы понять что мы на самом деле хотели "вставить", чтобы уйти и "съесть" первую аргумент - таким образом, становится функцией, которая хочет работать только на [t] (и "пересекают" их с "1" на всех возможных позициях).

Но где это было указано? Как GHC знал об этом, когда мы вызывали:

*Main> (concatMap . ins) 1 [[2,3]]
[[1,2,3],[2,1,3],[2,3,1]]

Была ли подпись "ins" каким-то образом передана эта... политика "есть мой первый аргумент"?

*Main> :info ins
ins :: t -> [t] -> [[t]]        -- Defined at b.hs:1:0-2

Я не вижу ничего особенного - "ins" - это функция, которая принимает "t", список 't', и приступает к созданию списка со всеми "interpersals". Ничего о том, чтобы "съесть свой первый аргумент и выкормить его".

Так что... Я озадачен. Я понимаю (через час взглянуть на код!), Что происходит, но... Бог всемогущий... Возможно, GHC пытается понять, сколько аргументов он может "очистить"?

  let try with no argument "curried" into "ins",
  oh gosh, boom, 
  let try with one argument "curried" into "ins",
  yep, works,
  that must be it, proceed)

Снова - yikes...

И поскольку я всегда сравниваю языки, которые я изучаю, с тем, что я уже знаю, как бы "вставить" в Python?

a=[2,3]
print [a[:x]+[1]+a[x:] for x in xrange(len(a)+1)]

[[1, 2, 3], [2, 1, 3], [2, 3, 1]]

Будьте честны, теперь... что проще?

Я имею в виду, я знаю, что я новичок в Haskell, но я чувствую себя идиотом... Глядя на 4 строки кода в течение часа и заканчивая тем, что компилятор... пытается интерпретировать все до тех пор, пока не найдет что-то, что "нажимает"?

Процитировать от смертельного оружия: "Я слишком стар для этого"...

Ответ 1

(f . g) x = f (g x)

Это верно. Вы пришли к выводу, что

(f . g) x y = f (g x y)

также должен быть правдой, но это не так. На самом деле верно следующее:

(f . g) x y = f (g x) y

который не является тем же.

Почему это так? Хорошо (f . g) x y совпадает с ((f . g) x) y, и поскольку мы знаем, что (f . g) x = f (g x), мы можем уменьшить это до (f (g x)) y, что снова совпадает с f (g x) y.

Итак, (concatMap . ins) 1 [[2,3]] эквивалентно concatMap (ins 1) [[2,3]]. Здесь нет никакой магии.

Другой способ приблизиться к этому - через типы:

. имеет тип (b -> c) -> (a -> b) -> a -> c, concatMap имеет тип (x -> [y]) -> [x] -> [y], ins имеет тип t -> [t] -> [[t]]. Поэтому, если мы используем concatMap как аргумент b -> c и ins как аргумент a -> b, тогда a становится t, b становится [t] -> [[t]] и c становится [[t]] -> [[t]]x= [t] и y= [t]).

Таким образом, тип concatMap . ins равен t -> [[t]] -> [[t]], что означает функцию, принимающую все и список списков (whatevers) и возвращающий список списков (одного и того же типа).

Ответ 2

Я хотел бы добавить свои два цента. Вопрос и ответ звучат так: . - это какой-то волшебный оператор, который делает странные вещи с повторными вызовами функций. Это не так. . - это просто композиция. Здесь реализация в Python:

def dot(f, g):
    def result(arg):
        return f(g(arg))
    return result

Он просто создает новую функцию, которая применяет g к аргументу, применяет f к результату и возвращает результат применения f.

Итак, (concatMap . ins) 1 [[2, 3]] говорит: создайте функцию concatMap . ins и примените ее к аргументам 1 и [[2, 3]]. Когда вы делаете concatMap (ins 1 [[2,3]]), вы говорите: примените функцию concatMap к результату применения ins к 1 и [[2, 3]] - совершенно по-другому, как вы выяснили ужасное сообщение об ошибке Haskell.

ОБНОВЛЕНИЕ: подчеркнуть это еще дальше. Вы сказали, что (f . g) x был другим синтаксисом для f (g x). Это неверно! . - это просто функция, поскольку функции могут иметь не-альфа-числовые имена (>><, .. и т.д., также могут быть именами функций).

Ответ 3

Ты переусердствуешь эту проблему. Вы можете все исправить, используя простые экваториальные рассуждения. Попробуйте это с нуля:

permute = foldr (concatMap . ins) [[]]

Это можно преобразовать тривиально:

permute lst = foldr (concatMap . ins) [[]] lst

concatMap можно определить как:

concatMap f lst = concat (map f lst)

Способ foldr работает в списке, это (например):

-- let lst = [x, y, z]
foldr f init lst
= foldr f init [x, y, z]
= foldr f init (x : y : z : [])
= f x (f y (f z init))

Так что-то вроде

permute [1, 2, 3]

становится:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 
    ((concatMap . ins) 2
       ((concatMap . ins) 3 [[]]))

Пусть работает через первое выражение:

(concatMap . ins) 3 [[]]
= (\x -> concatMap (ins x)) 3 [[]]  -- definition of (.)
= (concatMap (ins 3)) [[]]
= concatMap (ins 3) [[]]     -- parens are unnecessary
= concat (map (ins 3) [[]])  -- definition of concatMap

Теперь ins 3 [] == [3], поэтому

map (ins 3) [[]] == (ins 3 []) : []  -- definition of map
= [3] : []
= [[3]]

Итак, наше исходное выражение становится:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 
    ((concatMap . ins) 2
       ((concatMap . ins) 3 [[]]))
= (concatMap . ins) 1 
    ((concatMap . ins) 2 [[3]]

Пусть работает через

(concatMap . ins) 2 [[3]]
= (\x -> concatMap (ins x)) 2 [[3]]
= (concatMap (ins 2)) [[3]]
= concatMap (ins 2) [[3]]     -- parens are unnecessary
= concat (map (ins 2) [[3]])  -- definition of concatMap
= concat (ins 2 [3] : [])
= concat ([[2, 3], [3, 2]] : [])
= concat [[[2, 3], [3, 2]]]
= [[2, 3], [3, 2]]

Итак, наше исходное выражение становится:

foldr (concatMap . ins) [[]] [1, 2, 3]
= (concatMap . ins) 1 [[2, 3], [3, 2]]
= (\x -> concatMap (ins x)) 1 [[2, 3], [3, 2]]
= concatMap (ins 1) [[2, 3], [3, 2]]
= concat (map (ins 1) [[2, 3], [3, 2]])
= concat [ins 1 [2, 3], ins 1 [3, 2]] -- definition of map
= concat [[[1, 2, 3], [2, 1, 3], [2, 3, 1]], 
          [[1, 3, 2], [3, 1, 2], [3, 2, 1]]]  -- defn of ins
= [[1, 2, 3], [2, 1, 3], [2, 3, 1], 
   [1, 3, 2], [3, 1, 2], [3, 2, 1]]

Ничего волшебного здесь. Я думаю, вы, возможно, были смущены, потому что легко предположить, что concatMap = concat . map, но это не так. Точно так же это может выглядеть как concatMap f = concat . (map f), но это тоже не так. Рациональное рассуждение покажет вам, почему.