Здравствуйте, samius, Вы писали:
ARK>>>>Не уверен, что в определении Sinclair речь идет только о хаскеле. Оно общего вида. Если функция помечена соответствующим образом — она грязная (или, наоборот, чистая). S>>>Не уверен, что можно сформулировать "принимает IO" в общем виде. printf никакого IO не принимает и ничем не помечен. ARK>>Можно, почему нет. printf принимает и возвращает скрытый параметр "мир" (вполне можно так формализовать). S>Не думаю, что Sinclair подразумевал это в своем определении.
Не знаю. Пусть сам скажет.
ARK>>Мой поинт в том, что для рассуждений о чистоте — достаточно сигнатуры. Остальное проверить нельзя. S>
S>Что скажешь о чистоте по одной лишь сигнатуре с учетом того, что printf "чистая"? Вероятно, тоже будет чистой.
В С/C++ нет сигнатуры, определяющей чистоту. Поэтому флагом чистоты может быть только документация. Если там сказано, что эта функция чистая — значит, чистая. Если не сказано — значит, мы должны подозревать грязь. Можно строить предположения о чистоте из других факторов, но это самый ненадежный способ, ибо это та самая "чуйка", проверить которую в общем случае нельзя.
S>>>Но это не определение, это лишь метка. Не метка делает функцию чистой или грязной.
ARK>>Да, метка. Метки бывают разные, например "pure" или слово в документации. Прикол в том, что именно "метка" и определяет чистоту. Или что для тебя делает UnsafePerformIO грязным? Ты его "проверяешь"? Но не в виртуальной машине, ведь это нечестно? S>unsafePerformIO указывает на то, что функция с большой вероятностью либо берет данные из мира, либо кладет туда данные. Иначе зачем ей там быть?
Так unsafePerformIO грязен или нет?
ARK>>>>Так это то же самое же. S>>>Нет, т.к. результат вычисления твоей putStrLn1 нельзя закэшировать. Он void + грязь. ARK>>Почему, можно. Есть прямое преобразование к Action. S>преобразование к Action не выполняет код. Это не результат вызова. Это взятие "адреса".
Я о том, что можно "закешировать" результат Console.WriteLine через putStrLn1.
ARK>>>>Там об этом не сказано. Тогда что же такое "ввод-вывод" и "побочные эффекты"? Получается, это не то, что мы можем увидеть глазами или пощупать руками, ведь окружение может влиять на это. Тогда что это такое? Это дергание определенных функций операционной системы (или окружения в широком смысле)? Так? S>>>Опять-таки, если в коде условного оператора сложения строк или еще чего-то не написано дергать функции ОСи или окружения, но окружение при выполнении этого оператора само что-то дергает для отладки или слежения, то проблема ли это чистоты оператора сложения? Ведь он-то сам лишнего не дергает. Он чист и пользоваться его чистотой мы можем в полной мере, если исключим из внимания инициативу окружения, которая будет записывать что-то про регистры CPU в файл. Нам для разработки программы такая функция окружения может быть и вовсе неизвестна. Какой смысл концентрироваться на ней, избегая кэширования или переставления ? ARK>>Совершенно верно, посторонняя активность — это не проблема оператора сложения. И пользоваться его чистотой (декларируемой) мы можем, это как раз то, о чем я говорю. Но и тут тот же вопрос — что такое "лишнее", что может дернуть оператор сложения? Откуда мы знаем, "лишняя" функция или нет? ARK>>Ну и вопросы, собственно, остались без ответа (выделено). S>Ты так и не дошел до википедии? Там есть статья, ссылка на странице чистоты функции.
В википедии, если не ошибаюсь, сказано об эффектах, которые видны окружению. Поправь, если я не прав. Стало быть, в подходящем окружении ввода-вывода не будет, даже если мы будем дергать соответствующие функции. Так?
a> main world0 = let (a, world1) = putStr "Hello" world0
a> (b, world2) = putStr "World" world1
a> (c, world3) = putStr "Rsdn" world1 -- world1!
a> in ((), world3)
a>
a> с неочевидным поведением.
Не совсем понял каким образом. putStr не берёт на вход мир, она просто возвращает некий тип. И кроме тех операций, что определены в описании типа ничего сделать особо и нельзя. А в данном примере единственно что можно сделать это скомбинировать последовательно два таких типа в указанном порядке "IO a -> IO b -> IO b".
Вообще в этом обсуждении о чистоте я не понимаю почему все привязались именно к IO и что именно она какая-то особая и каким-то магическим образом меняет чистоту функции. Ведь это просто монада, как и скажем List. Никакой принципиальной разницы с т.з. языка нет.
А все функции в haskell чисты просто по определению, по спеке языка.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>По-моему ты путаешь немного определение чистоты и объяснение флага в языке.
ARK>А разве в языке есть отдельное понятие "чистая функция", не описываемое флагом? По-моему, эти вещи взаимозаменяемы и употребляются вместе: https://dlang.org/spec/function.html#pure-functions
В том и дело, что из отсутствия собственного определения чистоты ты вывел что описание флага — и есть определение чистоты для D языка.
S>>Ну так это всего лишь такой же флаг, как const. От того, что ты не пометишь им чего-нибудь, никаких изменений данных от этого не появится. Речь лишь о проверке компилятора и только. Права программиста тут не при чем. По рукам ему никто не даст, если он закэширует результат не помеченного pure "синуса".
ARK>Если пометить, то появится, мемоизация начнет работать. Функции без "pure" — с точки зрения языка равны грязным. Язык отделяет чистые функции от грязных, и делает это на основе модификатора "pure".
Нет, функции с точки зрения языка грязными не являются, просто не помечены флагом, разрешающим их использование в других функциях, помеченных флагом. Еще раз, язык не вводит свое определение чистоты. А вводит лишь метку и правила ее выставления.
ARK>>>Это нормально, как и аргументы могут быть перепутаны. Только вот смысла проверять все это — никакого (за исключением случаев, когда есть основания подозревать, что что-то не так), а иногда и не только смысла, но и возможности нет. При этом "декларативная" чистота при программировании всегда остается полезной, как и другие контракты. S>>С этим никаких проблем.
ARK>То есть декларативная (указанная в сигнатуре или документации) чистота полезна всегда (как один из контрактов функции)?
До определенной степени полезной при отсутствии причин не доверять такой информации.
S>>Большую чуйку не нужно иметь, что бы понять, что SendMessageA какая-нибудь что-то отчебучит, кроме возврата кода ошибки. Что-то такое, чего от синуса ждать не приходится.
ARK>Но все равно это чуйка. А формальный критерий есть? "Лишние" функции — это случайно не те функции, которые постулированы (документацией или модификаторами) как грязные?
Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное.
int foo(string str) => new []{0}.GetHashCode();
foo("hello").Dump();
foo("hello").Dump();
Здравствуйте, samius, Вы писали:
ARK>>А разве в языке есть отдельное понятие "чистая функция", не описываемое флагом? По-моему, эти вещи взаимозаменяемы и употребляются вместе: https://dlang.org/spec/function.html#pure-functions S>В том и дело, что из отсутствия собственного определения чистоты ты вывел что описание флага — и есть определение чистоты для D языка.
В "D" есть термин — "чистая функция". Ссылку на этот термин я давал. Это типа не чистые функции по мнению языка D, а какой-то абстрактный флаг? Это уже казуистика и жонглирование словами.
ARK>>Если пометить, то появится, мемоизация начнет работать. Функции без "pure" — с точки зрения языка равны грязным. Язык отделяет чистые функции от грязных, и делает это на основе модификатора "pure". S>Нет, функции с точки зрения языка грязными не являются, просто не помечены флагом, разрешающим их использование в других функциях, помеченных флагом. Еще раз, язык не вводит свое определение чистоты. А вводит лишь метку и правила ее выставления.
Тогда с таким подходом в хаскеле тоже чистых функций нет. Да и вообще их нет нигде, ни в одном языке. Везде только флаги и собственные термины. Верно?
S>>>Большую чуйку не нужно иметь, что бы понять, что SendMessageA какая-нибудь что-то отчебучит, кроме возврата кода ошибки. Что-то такое, чего от синуса ждать не приходится. ARK>>Но все равно это чуйка. А формальный критерий есть? "Лишние" функции — это случайно не те функции, которые постулированы (документацией или модификаторами) как грязные? S>Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное. S>
S>int foo(string str) => new []{0}.GetHashCode();
S>foo("hello").Dump();
S>foo("hello").Dump();
S>
Мне этот кусок кода мало о чем говорит. Как ты понимаешь, грязная ли функция Dump? Тебе ее название не нравится? Ты или "веришь" сразу, что она грязна, или лезешь дальше вглубь в поисках... чего именно? Можешь формально определить, что ты ищешь в коде? Давно это пытаюсь выяснить, но ты никак не ответишь четко.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
ARK>>>Мой поинт в том, что для рассуждений о чистоте — достаточно сигнатуры. Остальное проверить нельзя.
S>>Что скажешь о чистоте по одной лишь сигнатуре с учетом того, что printf "чистая"? Вероятно, тоже будет чистой.
ARK>В С/C++ нет сигнатуры, определяющей чистоту. Поэтому флагом чистоты может быть только документация. Если там сказано, что эта функция чистая — значит, чистая. Если не сказано — значит, мы должны подозревать грязь. Можно строить предположения о чистоте из других факторов, но это самый ненадежный способ, ибо это та самая "чуйка", проверить которую в общем случае нельзя.
То есть, одной сигнатуры недостаточно.
S>>>>Но это не определение, это лишь метка. Не метка делает функцию чистой или грязной.
ARK>>>Да, метка. Метки бывают разные, например "pure" или слово в документации. Прикол в том, что именно "метка" и определяет чистоту. Или что для тебя делает UnsafePerformIO грязным? Ты его "проверяешь"? Но не в виртуальной машине, ведь это нечестно? S>>unsafePerformIO указывает на то, что функция с большой вероятностью либо берет данные из мира, либо кладет туда данные. Иначе зачем ей там быть?
ARK>Так unsafePerformIO грязен или нет?
Чисто теоретически это бэкдор, маркер. И только лишь. В сочетании с вычислениями, не делающими сайд-эффектов и не принимающими значения из мира, он будет чист. В сочетании с грязными вычислениями — грязен.
Но на практике он меняет окружение при выполнении. Пользуется примитивами синхронизации многопоточного окружения. В однопоточной программе вряд ли имеет смысл обращать на это внимание. Т.е. зависит от...
ARK>>>>>Так это то же самое же. S>>>>Нет, т.к. результат вычисления твоей putStrLn1 нельзя закэшировать. Он void + грязь. ARK>>>Почему, можно. Есть прямое преобразование к Action. S>>преобразование к Action не выполняет код. Это не результат вызова. Это взятие "адреса".
ARK>Я о том, что можно "закешировать" результат Console.WriteLine через putStrLn1.
Кавычки должны быть минимум 24 шрифтом. И слово "закэшировать" не более чем 6-м.
ARK>>>Совершенно верно, посторонняя активность — это не проблема оператора сложения. И пользоваться его чистотой (декларируемой) мы можем, это как раз то, о чем я говорю. Но и тут тот же вопрос — что такое "лишнее", что может дернуть оператор сложения? Откуда мы знаем, "лишняя" функция или нет? ARK>>>Ну и вопросы, собственно, остались без ответа (выделено). S>>Ты так и не дошел до википедии? Там есть статья, ссылка на странице чистоты функции.
ARK>В википедии, если не ошибаюсь, сказано об эффектах, которые видны окружению. Поправь, если я не прав. Стало быть, в подходящем окружении ввода-вывода не будет, даже если мы будем дергать соответствующие функции. Так?
Да, наверное, можно так сказать. Что в подходящем окружении можно не бояться определенных сайд-эффектов (или что для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять). Но что нам делать с тем, что программа должна написать нечто вычисленное в файл/консоль, что мы хотим увидеть? У нее же должно быть какое-то желаемое наблюдаемое поведение кроме вырожденного (никакого)? Стоит избавиться от всех сайд-эффектов, и программу можно даже не писать, не то что бы компилировать и запускать.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>В том и дело, что из отсутствия собственного определения чистоты ты вывел что описание флага — и есть определение чистоты для D языка.
ARK>В "D" есть термин — "чистая функция". Ссылку на этот термин я давал. Это типа не чистые функции по мнению языка D, а какой-то абстрактный флаг? Это уже казуистика и жонглирование словами.
Unlike other functional programming languages, D's pure functions allow modification of the caller state through their mutable parameters.
Видишь, есть сноска что в D pure функции не такие, как в других языках. Но здесь определения pure D функций нет, тут написано, чем они отличаются.
ARK>>>Если пометить, то появится, мемоизация начнет работать. Функции без "pure" — с точки зрения языка равны грязным. Язык отделяет чистые функции от грязных, и делает это на основе модификатора "pure". S>>Нет, функции с точки зрения языка грязными не являются, просто не помечены флагом, разрешающим их использование в других функциях, помеченных флагом. Еще раз, язык не вводит свое определение чистоты. А вводит лишь метку и правила ее выставления.
ARK>Тогда с таким подходом в хаскеле тоже чистых функций нет. Да и вообще их нет нигде, ни в одном языке. Везде только флаги и собственные термины. Верно?
С чего бы? Оператор сложения как был чистым, таким и остался. С флагом или без. Естественно, с оговоркой до перечня определенных сайд-эффектов.
S>>Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное. S>>
S>>int foo(string str) => new []{0}.GetHashCode();
S>>foo("hello").Dump();
S>>foo("hello").Dump();
S>>
ARK>Мне этот кусок кода мало о чем говорит. Как ты понимаешь, грязная ли функция Dump? Тебе ее название не нравится? Ты или "веришь" сразу, что она грязна, или лезешь дальше вглубь в поисках... чего именно? Можешь формально определить, что ты ищешь в коде? Давно это пытаюсь выяснить, но ты никак не ответишь четко.
Dump-то безусловно грязная. Я не о ней, я о foo, которая грязная без обращения к постулированно грязным функциям.
Здравствуйте, AlexRK, Вы писали:
ARK>Насчет трактовки Sinclair можем у него самого спросить. Ау, Sinclair!
Да я вообще уже жалею, что ввязался в спор выше уровня моего понимания
Как тут уже верно заметили, я-то ожидал, что IO — это "контейнер для мира".
А на самом деле, IO — вроде бы что-то типа
Function<World, World>
(простите мой Редмондский).
Тогда я не вполне понимаю, как правильно строить свои "грязные" функции. То есть синус у нас, получается, возвращает значение и трансформацию, которую он хочет применить к миру.
Ок, тогда мне не совсемпонятно, как должна быть устроена функция "грязный квадрат синуса", которую мы пытаемся построить из грязного синуса так, чтобы на консоль продолжали логгироваться применяемые аргументы.
По идее, мне надо значение возвести в квадрат, а трансформации скомпоновать:
С одной стороны, всё становится более симметричным; с другой стороны, непонятно, как это работает — в том смысле, кто меня обяжет это делать, вместо того чтобы просто вернуть "нескомпонованный" вариант трансформации. Или вообще просто проложить на него с прибором, и никаких трансформаций не возвращать — обойтись значением .
Нутром чую, что это связано с монадами
Ну и это — вопрос "чистоты" функций в такой трактовке претерпевает существенные изменения.
В том смысле, что сам синус, вроде как, мир-то не меняет — он так, возвращает желательные трансформации мира.
Но как тут уже правильно заметили коллеги, это же всего лишь вопрос каррирования — в ФП функция, которая трансформирует мир, мало чем отличается от функции, которая возвращает трансформацию мира
С такой точки зрения, определение должно быть скорректировано как-то так, что "грязными считаются все функции, возвращаюшие IO". Ну и как бы всё — опять мы остаёмся на уровне сигнатуры.
Т.к. функции, не предлагаюшие трансформаций миру, мир поменять не могут. Стало быть, с ними можно обращаться относительно вольно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, AlexRK, Вы писали:
S>Как тут уже верно заметили, я-то ожидал, что IO — это "контейнер для мира". S>А на самом деле, IO — вроде бы что-то типа
Function<World, World>
(простите мой Редмондский).
S>Тогда я не вполне понимаю, как правильно строить свои "грязные" функции. То есть синус у нас, получается, возвращает значение и трансформацию, которую он хочет применить к миру. S>Ок, тогда мне не совсемпонятно, как должна быть устроена функция "грязный квадрат синуса", которую мы пытаемся построить из грязного синуса так, чтобы на консоль продолжали логгироваться применяемые аргументы.
В фп обычно не пишут такие грязные вещи. Это неудобно. Но есть все, что бы поднять (от lift) чистый синус в нужную монаду, как если бы мы хотели список или Option спроектировать с помощью функции sin.
Соответствующий метод, если не ошибаюсь, выглядит так
>:type fmap
fmap :: Functor f => (a -> b) -> f a -> f b
>:type fmap sin
fmap sin :: (Functor f, Floating b) => f b -> f b
Т.е. для заданного класса монады (Functor f, например, IO a) мы получим на вход функцию a->b (наш синус) и получим в результате функцию, принимающую IO a и отдающую IO b. Так же sin можно поднять и в монаду List и в другие, разумеется.
S>По идее, мне надо значение возвести в квадрат, а трансформации скомпоновать: S>
Здесь надо поднять синус, поднять квадрат, потом их связать bind-ом (аналог SelectMany в C# для Enumerable).
S>С одной стороны, всё становится более симметричным; с другой стороны, непонятно, как это работает — в том смысле, кто меня обяжет это делать, вместо того чтобы просто вернуть "нескомпонованный" вариант трансформации. Или вообще просто проложить на него с прибором, и никаких трансформаций не возвращать — обойтись значением . S>Нутром чую, что это связано с монадами
Оно и есть.
S>Ну и это — вопрос "чистоты" функций в такой трактовке претерпевает существенные изменения. S>В том смысле, что сам синус, вроде как, мир-то не меняет — он так, возвращает желательные трансформации мира. S>Но как тут уже правильно заметили коллеги, это же всего лишь вопрос каррирования — в ФП функция, которая трансформирует мир, мало чем отличается от функции, которая возвращает трансформацию мира
Собственно, поднятый в монаду синус — это есть функция, которая примет IO действие и вернет IO действие, вычисляющее синус от значения, полученного действия на входе. По аналогии со списком — возьмет список чисел, вычислит для каждого синус, вернет список результатов синусов, соответствующий списку на входе.
S>С такой точки зрения, определение должно быть скорректировано как-то так, что "грязными считаются все функции, возвращаюшие IO". Ну и как бы всё — опять мы остаёмся на уровне сигнатуры.
Возвращать IO — не грязь. Грязь — это выполнять IO, т.е. крутить и трансформировать мир. Т.е. поднятый в IO синус будет иметь тип
(Floating a) => IO a -> IO a
Т.е. когда ему дадут трансформацию, получающую значение с плавающей точкой, он вернет трансформацию, получающую значение с плавающей точкой. На этом функция "грязного" синуса выполнена. Здесь все детерминировано, и никаких следов в мире еще нет. Осталось лишь крутануть мир через вычисление в результате этой функции. Вот это крутануть — это и есть грязь. S>Т.к. функции, не предлагаюшие трансформаций миру, мир поменять не могут. Стало быть, с ними можно обращаться относительно вольно.
С самими трансформациями мы тоже можем вольно работать, строить из них другие трансформации, т.е. связывать, комбинировать. Не должны лишь только запускать в них мир. Именно это делает unsafePerformIO и пускач программы на хаскеле, который итоговую трансформацию получает из main.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, AlexRK, Вы писали:
ARK>>Насчет трактовки Sinclair можем у него самого спросить. Ау, Sinclair! S>Да я вообще уже жалею, что ввязался в спор выше уровня моего понимания
S>Как тут уже верно заметили, я-то ожидал, что IO — это "контейнер для мира". S>А на самом деле, IO — вроде бы что-то типа
Function<World, World>
(простите мой Редмондский). S>Тогда я не вполне понимаю, как правильно строить свои "грязные" функции. То есть синус у нас, получается, возвращает значение и трансформацию, которую он хочет применить к миру. S>Ок, тогда мне не совсемпонятно, как должна быть устроена функция "грязный квадрат синуса", которую мы пытаемся построить из грязного синуса так, чтобы на консоль продолжали логгироваться применяемые аргументы.
Во, на редмондском
using static IOModule;
public struct Unit
{
public static Unit Value => default;
}
public static class IOModule
{
internal class World { }
public class IO<a>
{
internal readonly Func<World, (a, World)> transform;
internal IO(Func<World, (a, World)> transform)
{
this.transform = transform;
}
}
public static IO<Unit> PutStr(string s) => new IO<Unit>(world =>
{
Console.WriteLine(s);
return (Unit.Value, new World());
});
public static IO<string> GetStr() => new IO<string>(world =>
{
return (Console.ReadLine(), new World());
});
public static IO<a> Return<a>(a x) => new IO<a>(world => (x, world));
public static IO<b> Bind<a, b>(this IO<a> m, Func<a, IO<b>> f) => new IO<b>(world0 =>
{
var (x, world1) = m.transform(world0);
return f(x).transform(world1);
});
public static IO<b> SelectMany<a, b>(this IO<a> m, Func<a, IO<b>> f) => m.Bind(f);
public static IO<c> SelectMany<a, b, c>(this IO<a> m, Func<a, IO<b>> f, Func<a, b, c> resultSelector) => new IO<c>(world0 =>
{
var (x, world1) = m.transform(world0);
var (y, world2) = f(x).transform(world0);
return (resultSelector(x, y), world2);
});
public static IO<b> Select<a, b>(this IO<a> m, Func<a, b> f) => m.SelectMany(x => Return(f(x)));
public static void ExecuteAsMain(this IO<Unit> m) => m.transform(new World());
}
class Program
{
public static IO<double> Sin(double x) =>
from _ in PutStr(x.ToString())
select Math.Sin(x);
public static IO<double> Sin_another(double x) =>
PutStr(x.ToString()).Bind(_ => Return(Math.Sin(x)));
public static IO<double> Sin2(double x) =>
from a in Sin(x)
from b in Sin(x)
select a * b;
public static IO<double> Sin2_another1(double x) =>
from a in Sin(x)
select a * a;
public static IO<double> Sin2_another2(double x) =>
Sin_another(x).Bind(a => Return(a * a));
static void Main()
{
var main = from _1 in PutStr("What angle?")
from s in GetStr()
let x = double.Parse(s) * Math.PI / 180.0
from result in Sin2(x)
from _2 in PutStr($"Result: {result}")
select Unit.Value;
Console.WriteLine("Start execute main funсtion");
main.ExecuteAsMain();
Console.ReadKey();
}
}
Здравствуйте, artelk, Вы писали:
A>Во, на редмондском A>
A> public static IO<double> Sin_another(double x) =>
A> PutStr(x.ToString()).Bind(_ => Return(Math.Sin(x)));
A> public static IO<double> Sin2(double x) =>
A> from a in Sin(x)
A> from b in Sin(x)
A> select a * b;
A> public static IO<double> Sin2_another1(double x) =>
A> from a in Sin(x)
A> select a * a;
A>
Хороший пример, спасибо. Тут собственно видно, что поднятый синус чист как слеза, хотя и возвращает IO. Сам никаких трансформаций не запускает. Детерминирован и не делает сайд-эффектов.
Здравствуйте, samius, Вы писали:
S>>>Что скажешь о чистоте по одной лишь сигнатуре с учетом того, что printf "чистая"? Вероятно, тоже будет чистой. ARK>>В С/C++ нет сигнатуры, определяющей чистоту. Поэтому флагом чистоты может быть только документация. Если там сказано, что эта функция чистая — значит, чистая. Если не сказано — значит, мы должны подозревать грязь. Можно строить предположения о чистоте из других факторов, но это самый ненадежный способ, ибо это та самая "чуйка", проверить которую в общем случае нельзя. S>То есть, одной сигнатуры недостаточно.
Достаточно. Эта функция будет грязной, т.к. не указано обратное. И мы должны ее таковой считать и обращаться с ней соответствующим образом.
А вот если у синуса не будет явного указания, что он чист — то это хуже. Умом мы вроде как понимаем, что синус должен быть чистым, но это не подтверждается декларацией. Формально мы должны или считать его грязным, или смотреть исходники. А в исходниках, если они есть, искать грязь, т.е. функции, аксиоматически постулированные как грязные (если не найдем — значит можем заключить, что синус "декларативно" чист).
ARK>>В википедии, если не ошибаюсь, сказано об эффектах, которые видны окружению. Поправь, если я не прав. Стало быть, в подходящем окружении ввода-вывода не будет, даже если мы будем дергать соответствующие функции. Так? S>Да, наверное, можно так сказать. Что в подходящем окружении можно не бояться определенных сайд-эффектов (или что для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять).
Не просто "для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять", а "сайд-эффекты как таковые не существуют в подходящем окружении", потому что само определение сайд-эффектов завязано на окружение. Получается, в таком "подходящем окружении" функция, согласно википедии, будет чистой. А в другом окружении — нет. То есть википедийное определение чистоты имеет в себе зависимость от окружения.
S>Но что нам делать с тем, что программа должна написать нечто вычисленное в файл/консоль, что мы хотим увидеть? У нее же должно быть какое-то желаемое наблюдаемое поведение кроме вырожденного (никакого)? Стоит избавиться от всех сайд-эффектов, и программу можно даже не писать, не то что бы компилировать и запускать.
Можно сказать, что выполнение данной программы в таком окружении не имеет эффекта. Собственно, любой программе нужно какое-то специфическое окружение, на которое она рассчитана. Главный вопрос не в этом, а в том, должно ли окружение влиять на формальные рассуждения о семантических свойствах программы. Согласно википедийным определениям — должно. Мне это кажется неправильным.
UPD. Кстати, я, пожалуй, соглашусь, что putStr является чистой функцией. Я поковырял код и не нашел улик, доказывающих обратное. Например, вот этот код
import System.IO.Unsafe
putStrLn2 :: String -> IO ()
putStrLn2 (s) = do
let q = unsafePerformIO $ do { putStrLn " backdoor "; return " 1 "; }
putStrLn (s ++ q)
putStrLn1 = putStrLn2 "qwerty"
main = do
putStrLn1
putStrLn1
выдает:
qwerty backdoor
1
qwerty 1
т.е. можно считать результат вызова putStr закешированным. Значит, "оторвать" action от IO-функции таки можно, как минимум, теоретически. Как оно на самом деле происходит — непонятно, потому что никак нельзя вызвать какую-то "отладочную печать" изнутри функции. По крайней мере я не знаю, как это сделать. Через unsafePerformIO вот такую картину показывает.
Поэтому вопрос о чистоте putStr я закрываю — я согласен, что она чиста, в том числе в соответствии с моей трактовкой чистоты. Другой вопрос, что нам эта ее чистота не так уж сильно помогает, потому что нам интересно в основном то, что делает ее результат (action), а не то, что делает она сама (просто возвращает action).
Здравствуйте, samius, Вы писали:
S>Видишь, есть сноска что в D pure функции не такие, как в других языках. Но здесь определения pure D функций нет, тут написано, чем они отличаются.
По-моему, очевидно, что под "pure functions" понимаются функции с модификатором "pure". Иная трактовка бессмысленна.
S>>>Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное. S>>>
S>>>int foo(string str) => new []{0}.GetHashCode();
S>>>foo("hello").Dump();
S>>>foo("hello").Dump();
S>>>
ARK>>Мне этот кусок кода мало о чем говорит. Как ты понимаешь, грязная ли функция Dump? Тебе ее название не нравится? Ты или "веришь" сразу, что она грязна, или лезешь дальше вглубь в поисках... чего именно? Можешь формально определить, что ты ищешь в коде? Давно это пытаюсь выяснить, но ты никак не ответишь четко. S>Dump-то безусловно грязная. Я не о ней, я о foo, которая грязная без обращения к постулированно грязным функциям.
Не понял. Почему foo грязная? Если смотреть на код, то о ее чистоте можно судить только по статусу функций new и GetHashCode. Какими мы их постулируем (или выведем из постулированных) — такой и будет foo. Нет?
Выделение памяти можно в принципе считать и чистой операцией — это как решит создатель языка.
Здравствуйте, samius, Вы писали:
S>Хороший пример, спасибо. Тут собственно видно, что поднятый синус чист как слеза, хотя и возвращает IO. Сам никаких трансформаций не запускает. Детерминирован и не делает сайд-эффектов.
Пожалуйста! Но я все же настаиваю, что для Хаскела тот факт, что возвращается именно трансформация, а не ее результат, не принципиален в вопросе о чистоте. Внутренняя гипотетическая функция transform там тоже чиста. Она детерминирована (весь Мир передается ей в качестве параметра) и не делает никаких сайд-эффектов (т.к. все ее "эффекты" вовсе не "сайд", а включены в возвращаемое значение).
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>То есть, одной сигнатуры недостаточно.
ARK>Достаточно. Эта функция будет грязной, т.к. не указано обратное. И мы должны ее таковой считать и обращаться с ней соответствующим образом.
ARK>А вот если у синуса не будет явного указания, что он чист — то это хуже. Умом мы вроде как понимаем, что синус должен быть чистым, но это не подтверждается декларацией. Формально мы должны или считать его грязным, или смотреть исходники. А в исходниках, если они есть, искать грязь, т.е. функции, аксиоматически постулированные как грязные (если не найдем — значит можем заключить, что синус "декларативно" чист).
Т.е. процедура определения чистоты по декларациям ничем не лучше изучения исходников там, где чистота явно не указана.
ARK>Не просто "для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять", а "сайд-эффекты как таковые не существуют в подходящем окружении", потому что само определение сайд-эффектов завязано на окружение. Получается, в таком "подходящем окружении" функция, согласно википедии, будет чистой. А в другом окружении — нет. То есть википедийное определение чистоты имеет в себе зависимость от окружения.
От типа сайд-эффектов. Т.к. для некоторых сайд-эффектов нет зависимости даже от окружения. Ты не можешь дважды по одному адресу выделить память для результата. И результаты будут отличаться уже этим, когда мы захотим сравнить адреса полученных результатов (в случае, когда результат не кэшируется).
S>>Но что нам делать с тем, что программа должна написать нечто вычисленное в файл/консоль, что мы хотим увидеть? У нее же должно быть какое-то желаемое наблюдаемое поведение кроме вырожденного (никакого)? Стоит избавиться от всех сайд-эффектов, и программу можно даже не писать, не то что бы компилировать и запускать.
ARK>Можно сказать, что выполнение данной программы в таком окружении не имеет эффекта. Собственно, любой программе нужно какое-то специфическое окружение, на которое она рассчитана. Главный вопрос не в этом, а в том, должно ли окружение влиять на формальные рассуждения о семантических свойствах программы. Согласно википедийным определениям — должно. Мне это кажется неправильным.
Вот как определение непрерывности функции в точке следует начинать читать с "для любого заданного эпсилон". Так и тут, надо сперва определить область сайдэффектов. Потому как найдутся люди, которые скажут, ха, твоя программа выделила еще 12 байт памяти для хранения результата такой-то операции, сайд-эффект!!!! И это вычисление заняло столько-то тиков, которые убежали безвозвратно, вот тут пэйдж-фаулт, головка винта пошла в такой-то сектор... Сайд-эффект!!!!
Т.е. мы еще не добрались до того, что бы менять окружение радикальным образом, еще код функции не написали, а тут уже вопросы.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>Видишь, есть сноска что в D pure функции не такие, как в других языках. Но здесь определения pure D функций нет, тут написано, чем они отличаются.
ARK>По-моему, очевидно, что под "pure functions" понимаются функции с модификатором "pure". Иная трактовка бессмысленна.
Но в других языках нет такого модификатора. А в D — верно, те, что с модификатором, и компилятор им не подавился.
S>>>>Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное. S>>>>
S>>>>int foo(string str) => new []{0}.GetHashCode();
S>>>>foo("hello").Dump();
S>>>>foo("hello").Dump();
S>>>>
ARK>Не понял. Почему foo грязная? Если смотреть на код, то о ее чистоте можно судить только по статусу функций new и GetHashCode. Какими мы их постулируем (или выведем из постулированных) — такой и будет foo. Нет? ARK>Выделение памяти можно в принципе считать и чистой операцией — это как решит создатель языка.
Это как раз то, что можно считать, а можно не считать сайд-эффектом. Хэшкод массива в дотнете определен не содержимым массива, как в случае со строкой, а шапкой самого массива. Выделяем два массива нулевой длины — и их хэшкоды будут отличаться с очень большой вероятностью (но меньше 1-ы), даже при структурно равном содержимом. Итог — foo возвращает чаще разные результаты, чем одинаковые. И даже тот же самый аргумент не дает гарантии того же самого результата.
Т.е. еще раз — сигнатура — чистая. В грязные функции с IO не ходили. Взывали только чистые, включая GetHashCode(), Но чистый результат при этом не получили. И по исходникам не каждый скажет, что там грязь. Да и я в другом контексте разговора не факт, что заподозрю грязь в этой функции.
Здравствуйте, artelk, Вы писали:
A>Здравствуйте, samius, Вы писали:
S>>Хороший пример, спасибо. Тут собственно видно, что поднятый синус чист как слеза, хотя и возвращает IO. Сам никаких трансформаций не запускает. Детерминирован и не делает сайд-эффектов. A>Пожалуйста! Но я все же настаиваю, что для Хаскела тот факт, что возвращается именно трансформация, а не ее результат, не принципиален в вопросе о чистоте. Внутренняя гипотетическая функция transform там тоже чиста. Она детерминирована (весь Мир передается ей в качестве параметра) и не делает никаких сайд-эффектов (т.к. все ее "эффекты" вовсе не "сайд", а включены в возвращаемое значение).
Здесь уже зависит от угла зрения. Да, если допустить что мы все внутренние состояния программы спрятали за этой абстрактной веревочкой "мир" и ждем два байта из этого мира, то формально все будет чисто. Но за этим чистым фасадом, в нашем же процессе будут происходить вещи невероятной грязи. Мы же просто от них отвернулись, считая, что эти вещи неотъемлемые детали нового мира, в то время как сам мир об этих деталях ничего не знает. И детали о мире — тоже.
Можно считать и так и сяк и я бы не стал оспаривать другую точку зрения. А вот с тем, что грязна трансформация, а не производящая ее функция — тут надо наводить резкость.
Здравствуйте, samius, Вы писали:
ARK>>Достаточно. Эта функция будет грязной, т.к. не указано обратное. И мы должны ее таковой считать и обращаться с ней соответствующим образом. ARK>>А вот если у синуса не будет явного указания, что он чист — то это хуже. Умом мы вроде как понимаем, что синус должен быть чистым, но это не подтверждается декларацией. Формально мы должны или считать его грязным, или смотреть исходники. А в исходниках, если они есть, искать грязь, т.е. функции, аксиоматически постулированные как грязные (если не найдем — значит можем заключить, что синус "декларативно" чист). S>Т.е. процедура определения чистоты по декларациям ничем не лучше изучения исходников там, где чистота явно не указана.
Изучение исходников все равно сведется к декларациям на некотором низком уровне. Вопрос только в том, где остановиться — доверять тому, что вверху, или лезть еще глубже (если это возможно).
ARK>>Не просто "для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять", а "сайд-эффекты как таковые не существуют в подходящем окружении", потому что само определение сайд-эффектов завязано на окружение. Получается, в таком "подходящем окружении" функция, согласно википедии, будет чистой. А в другом окружении — нет. То есть википедийное определение чистоты имеет в себе зависимость от окружения. S>От типа сайд-эффектов. Т.к. для некоторых сайд-эффектов нет зависимости даже от окружения. Ты не можешь дважды по одному адресу выделить память для результата. И результаты будут отличаться уже этим, когда мы захотим сравнить адреса полученных результатов (в случае, когда результат не кэшируется).
Про конкретно память не уверен. Для этого надо в модели языка иметь понятие "ячейка памяти" и какие-то операции над этим. Если этого нет, то, по-моему, это нельзя считать сайд-эффектом.
ARK>>Можно сказать, что выполнение данной программы в таком окружении не имеет эффекта. Собственно, любой программе нужно какое-то специфическое окружение, на которое она рассчитана. Главный вопрос не в этом, а в том, должно ли окружение влиять на формальные рассуждения о семантических свойствах программы. Согласно википедийным определениям — должно. Мне это кажется неправильным. S>Вот как определение непрерывности функции в точке следует начинать читать с "для любого заданного эпсилон". Так и тут, надо сперва определить область сайдэффектов. Потому как найдутся люди, которые скажут, ха, твоя программа выделила еще 12 байт памяти для хранения результата такой-то операции, сайд-эффект!!!! И это вычисление заняло столько-то тиков, которые убежали безвозвратно, вот тут пэйдж-фаулт, головка винта пошла в такой-то сектор... Сайд-эффект!!!! S>Т.е. мы еще не добрались до того, что бы менять окружение радикальным образом, еще код функции не написали, а тут уже вопросы.
Ну типа того, да. И это действительно сайд-эффекты, просто учитывать их все смысла немного. Надо договориться, что есть сайд-эффекты применительно к чистоте. По моему мнению, это просто должно быть аксиоматически постулировано (собственно, тем самым мы одновременно приходим к постулированию того, что есть чистая функция).
Здравствуйте, samius, Вы писали:
S>>>>>Не только лишь они. Можно придумать что-то, что не постулировано как грязное, но ведет себя как грязное. S>>>>>
S>>>>>int foo(string str) => new []{0}.GetHashCode();
S>>>>>foo("hello").Dump();
S>>>>>foo("hello").Dump();
S>>>>>
ARK>>Не понял. Почему foo грязная? Если смотреть на код, то о ее чистоте можно судить только по статусу функций new и GetHashCode. Какими мы их постулируем (или выведем из постулированных) — такой и будет foo. Нет? ARK>>Выделение памяти можно в принципе считать и чистой операцией — это как решит создатель языка.
S>Это как раз то, что можно считать, а можно не считать сайд-эффектом. Хэшкод массива в дотнете определен не содержимым массива, как в случае со строкой, а шапкой самого массива. Выделяем два массива нулевой длины — и их хэшкоды будут отличаться с очень большой вероятностью (но меньше 1-ы), даже при структурно равном содержимом. Итог — foo возвращает чаще разные результаты, чем одинаковые. И даже тот же самый аргумент не дает гарантии того же самого результата. S>Т.е. еще раз — сигнатура — чистая. В грязные функции с IO не ходили. Взывали только чистые, включая GetHashCode(), Но чистый результат при этом не получили. И по исходникам не каждый скажет, что там грязь. Да и я в другом контексте разговора не факт, что заподозрю грязь в этой функции.
Ясно. В данном случае получается ошибка сигнатуры. Под сигнатурой здесь понимается только документация, потому что в С# нет никаких модификаторов чистоты, и по умолчанию каждую функцию мы должны считать грязной (потенциально). А вот если бы модификатор pure был, то компилятор не должен был бы пропустить такой код.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>Т.е. процедура определения чистоты по декларациям ничем не лучше изучения исходников там, где чистота явно не указана.
ARK>Изучение исходников все равно сведется к декларациям на некотором низком уровне. Вопрос только в том, где остановиться — доверять тому, что вверху, или лезть еще глубже (если это возможно).
Верно. Поэтому, решение о разметке, которую будет проверять компилятор, нельзя назвать плохим. Не знаю, можно ли взломать разметку в D, например сравнивая адреса объектов-результатов...
ARK>>>Не просто "для некого набора сайд-эффектов можно построить подходящее окружение, что сайд-эффекты не будут себя проявлять", а "сайд-эффекты как таковые не существуют в подходящем окружении", потому что само определение сайд-эффектов завязано на окружение. Получается, в таком "подходящем окружении" функция, согласно википедии, будет чистой. А в другом окружении — нет. То есть википедийное определение чистоты имеет в себе зависимость от окружения. S>>От типа сайд-эффектов. Т.к. для некоторых сайд-эффектов нет зависимости даже от окружения. Ты не можешь дважды по одному адресу выделить память для результата. И результаты будут отличаться уже этим, когда мы захотим сравнить адреса полученных результатов (в случае, когда результат не кэшируется).
ARK>Про конкретно память не уверен. Для этого надо в модели языка иметь понятие "ячейка памяти" и какие-то операции над этим. Если этого нет, то, по-моему, это нельзя считать сайд-эффектом.
Конечно, не все модели вычислений имеют понятие ячейки, да еще и с номером. Тем же клеточным автоматам достаточно понятия соседа (соседей), точный адрес там избыточен.
S>>Т.е. мы еще не добрались до того, что бы менять окружение радикальным образом, еще код функции не написали, а тут уже вопросы.
ARK>Ну типа того, да. И это действительно сайд-эффекты, просто учитывать их все смысла немного. Надо договориться, что есть сайд-эффекты применительно к чистоте. По моему мнению, это просто должно быть аксиоматически постулировано (собственно, тем самым мы одновременно приходим к постулированию того, что есть чистая функция).
Мы не можем постулировать все. Для однопоточной программы не важно то, что для многопоточной ой как важно. Т.е. только в зависимости от этого, у нас будут разные наборы. А сколько еще других аспектов (с разбегу не могу придумать).
ARK>Кстати, я там в апдейте написал про putStr.
Видел. Рад, что вопрос снят.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, samius, Вы писали:
S>>Т.е. еще раз — сигнатура — чистая. В грязные функции с IO не ходили. Взывали только чистые, включая GetHashCode(), Но чистый результат при этом не получили. И по исходникам не каждый скажет, что там грязь. Да и я в другом контексте разговора не факт, что заподозрю грязь в этой функции.
ARK>Ясно. В данном случае получается ошибка сигнатуры. Под сигнатурой здесь понимается только документация, потому что в С# нет никаких модификаторов чистоты, и по умолчанию каждую функцию мы должны считать грязной (потенциально). А вот если бы модификатор pure был, то компилятор не должен был бы пропустить такой код.
Такая разметка есть в дотнете (PureAttribute), но компилятор не занимается проверкой корректности его использования. Вряд ли будет в обозримом будущем, т.к. это все упрется в такие моменты как "должен ли быть массив istructuralequatable", которые изменить уже нельзя без поломки совместимости.