Pull to refresh

Comments 20

Я с функциональщиной не особо знаком, поэтому извините за возможно тупой вопрос, но


Зачем ограничиваться симметричными функциями а не влепить f(a,b) = g(b,a) и разворот списка?


def fold_left(foo, init, arr):
    foo2 = lambda a,b: foo(b,a)
    return fold_right(foo2, reverse(arr), start)

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

Вы правы, в статье подразумевается конечный список. В Коке для работы с бесконечными списками используется ко-индукция (это есть в книге Adam Chlipala: CPDT там работают несколько другие правила).

> > списки могут быть бесконечные
> Вы правы, в статье подразумевается конечный список.

вы либо «но» пропустили, либо он не прав…

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

Извините, что поздновато отвечаю.

Не имеем права мы так сделать, с точки зрения математики. Мы занимаемся формальной верификацией. У нас есть функция, написанная программистами. А мы должны доказывать ее свойства и характеристики для всех возможных входных данных (указанных программистами-авторами функции). Просто менять тело функции мы не можем. Это все равно, что написать

Теорема: для всех натуральных чисел N, 3 + N = 5.

Доказательств не требует. Принимать, как аксиому.

И опираясь на эту теорему пытаться дальше верифицировать код...

… кажется мы не понимаем друг друга


в статье вы написали, что если код написан через fold_right, то его доказывать легче и потратили много времени рассуждая как доказывать fold_left
я ж утверждаю, что fold_left по сути представим через fold_right и reverse — и можно применить уже известное решение


по сути всё как в анекдоте про математика, что выключает газ и выливает чайник — потому что приводится к предыдущей, уже решённой задаче

Ага! Я настроила уведомления и теперь отвечаю побыстрее!

Да! Вы правы! fold_left представим через fold_right. И я описала этот случай в статье, это самый простой случай. Когда сворачивающая функция внутри fold коммутативна.

Обратите внимание на приведенную в статье теорему из стандартной библиотеки

Theorem fold_symmetric : forall (A : Type) (f : A -> A -> A), (forall x y z : A, f x (f y z) =

f (f x y) z) ->

forall (a0 : A),

(forall y : A, f a0 y = f y a0) ->

forall (l : list A),

fold_left f l a0 = fold_right f a0 l.

То есть эта теорема и есть ваше (совершенно справедливое, но не для всех случаев верное) утверждение.

И эта теорема доказана и находится в стандартной библиотеке Coq и ее можно использовать. Но, видите, у этой теоремы есть предпосылка о внутренней сворачивающей функции. Что результат ее вычислений не изменится от перемены мест аргументов.

Далее, там, где я "потратила много времени, рассуждая как доказывать fold_left" я показывала, что делать в случаях, когда просто переписать fold_left на fold_right не получится. Не получится, потому, что сворачивающая функция внутри не коммутативна, то есть мы не имеем права переписать с точки зрения математики, результаты работы функций будут разные.

(3 + 2) все равно, что (2 + 3) - это коммутативная операция

Но (3 - 2) это не то же самое, что (2 - 3), понимаете?

А мы занимаемся формальной верификацией. Мы не можем изменять исходный код верифицируемых нами функций бездоказательно, так как нам удобно. Мы же в коде баги ищем. Мы для некоторых функций просто не сможем применить теорему fold_symmetric потому, что, чтобы ее применить нужно доказать потом еще, что внутренняя сворачивающая функция удовлетворяет требованию. А если это не так - это просто недоказуемо.

Но спасибо за вопрос, хороший вопрос (хоть ответ на него и есть в статье, а видно, сложновато написано).

А если обёртку меняющую аргументы местами вставить? Тогда коммутативность не нужна


Пусть а_б = б-а
(3-2) = (2_3)
(5-(3-2)) = ((2_3)_5)


Мы не можем изменять исходный код верифицируемых нами функций бездоказательно

так я и не меняю
я лишь представляю уже имеющийся как набор других компонент

На здоровье. Только если сможете свои утверждения доказать. Чтобы Coq позволил в конце "Qed" написать. Ч.Т.Д - по-нашему.

Можно еще некоторые вещи аксиоматизировать (объявлять аксиомами), а потом использовать их в доказательствах.

Но эдак далеко можно зайти... зайти далеко, а никуда не прийти.

Все утверждения, которые мы используем в доказательствах должны быть доказаны.

Блин, ну не понимаю я вас совершенно


В статье написали — fold_left фигню с аккумулятором творит, а вот fold_right молодец. Чтобы это решить… свяжем себе руки и выбросим нафиг часть функций.


Я конечно тоже могу сказать "На здоровье, выкидывайте. Ешьте свой кактус", но блин


Вот вам предлагают "обертка+разворот+fold_right == fold_left".
Ладно бы вы придрались к развороту. "Какой-нибудь функциональный закон нарушает"
Ладно бы придрались к обёртке. "Автоматическое доказывание через другую входную переменную не идёт"


Но ваши аргументы:
"нельзя переписывать код" — когда сами в статье fold_left через fold_right выражаете — и
"Как сможете доказать, тогда и будем обсуждать что не накладывать на функцию ограничение коммутативности вообще возможно" — вообще напоминает "как убьют — тогда и звоните"


Вон питоновский код в первом комменте. Вон пример с а_б.
Оба показывают как это работает. Приём работает.
Проблемы которые видите вы — я не вижу.
И поэтому я прошу Вас, великого мастера функциональщины, объяснить простому смертному Мне в чём проблема

Да что ж ты будешь делать... вы в каком часовом поясе?

Спасибо вам за питоновский код! Но на питоне, к сожалению, мы не можем ничего доказать (питон не является одним из интерактивных программных средств доказательства теорем).

Впрочем, я рада, что вас так заинтересовала формальная верификация.

У меня в тексте статьи приведен код на Coq. Это очень простой (как топор) пример, исключительно демонстративного характера.

Докажите, пожалуйста, лемму transformation вашим способом.

то бишь "обертка+разворот+fold_right == fold_left".

Видите, как я там мучаюсь, какое доказательство длинное вышло.

Код, пожалуйста, выложите на гитхаб и ссылочку мне пришлите, чтобы я могла скомпилировать на своей машине ваше решение.

А я завтра напишу вводную статью о формальной верификации. Что это вообще такое и зачем она нужна. По рукам?

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

А потом подобрать такую функцию f и такой список, для которых ваше равенство выполняться не будет. То есть попытаться найти/подобрать опровержение.

Это прежде, чем формулировать это все на Coq. Это будет быстрее и проще. И должно занять не так много времени.

Написал сложное доказательство, в котором естественно возникают зависимые типы (некоторый тип данных естественным образом делится на "слои", индексированные натуральными числами). Итого, есть типы B и A n, где n:nat. Хочу доказать, что B есть сумма всех A n. Строю функции

foo (n:nat) (a: A n) : B

rang (b:B) : nat

bar (b:B) : A (rang b)

Доказываю

forall (b:B), foo (rang b) (bar b) = b

forall (n:nat) (a: A n), rang (foo n a) = n

Но когда выписываю теорему

forall (n:nat) (a: A n), bar (foo n a) = a

получаю

"The term bar (foo n a) has type A (rang (foo n a)) while it is expected to have type A n". 

Спрашиваю на форуме, отвечают -- надо присобачить тип равенства. Прикидываю, как его присобачить, понимаю, что доказательство (и без того сложное) станет сложнее в два раза, бросаю Coq нафиг. Хотя, сделан Coq хорошо, это я такой хитроумный.

А вы пробовали выложить код на гитхаб, чтобы его можно было быстренько скачать и скомпилировать и задать свои вопросы тут: https://coq.zulipchat.com/

Сообщество кок отвечает моментально, очень помогают и поддерживают друг друга. Вероятно потому, что их популяция очень мала (редки как бенгальские тигры или белоснежные барсы).

Да, это проблема. Найти ответы на свои вопросы в интернете (на сегодняшний день) в этой сфере зачастую невозможно. Нужно перелопатить самостоятельно документацию, обучающие книги. А это требует колоссального количества времени и сил.

Она есть и в Haskell, и в OCaml и в Rust. Используется чаще, чем fold_right, вероятно потому, что с ее помощью проще писать эффективный код.

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

Если операция свёртки энергичная (как в набившем оскомину примере суммы списка через свёртку), то левая свёртка эффективнее, потому что не плодит thunk-и.

Точно! Спасибо за замечание. Я ее верифицировала для языка OCaml, вот почему я написала "чаще". А OCaml не ленивый (ленность там отдельно подключать нужно). Кто чаще ее использует зависит от того, какой язык используется - ленивый нет. Можно я статью исправлю с вашим уточнением.

Sign up to leave a comment.

Articles