Я должен признать, что я не знаю много о функциональном программировании. Я читал об этом здесь и там, и поэтому узнал, что в функциональном программировании функция возвращает один и тот же вывод для одного и того же ввода, независимо от того, сколько раз вызывается функция. Это в точности как математическая функция, которая оценивает один и тот же выход для того же значения входных параметров, которое входит в выражение функции.
Например, рассмотрим это:
f(x,y) = x*x + y; // It is a mathematical function
Независимо от того, сколько раз вы используете f(10,4)
, его значение всегда будет 104
. Таким образом, где бы вы ни писали f(10,4)
, вы можете заменить его 104
без изменения значения всего выражения. Это свойство называется ссылочной прозрачностью выражения.
Как говорит Википедия ( ссылка ),
И наоборот, в функциональном коде выходное значение функции зависит только от аргументов, которые вводятся в функцию, поэтому вызов функции f дважды с одним и тем же значением для аргумента x даст одинаковый результат f (x) оба раза.
Может ли функция времени (которая возвращает текущее время) существовать в функциональном программировании?
Если да, то как оно может существовать? Не нарушает ли это принцип функционального программирования? Это особенно нарушает ссылочную прозрачность, которая является одним из свойств функционального программирования (если я правильно понимаю).
А если нет, то как узнать текущее время в функциональном программировании?
Ответы:
Это можно объяснить еще одним способом: ни одна функция не может получить текущее время (поскольку оно постоянно меняется), но действие может получить текущее время. Допустим,
getClockTime
это константа (или нулевая функция, если хотите), которая представляет действие получения текущего времени. Это действие одинаково каждый раз, независимо от того, когда оно используется, поэтому оно является настоящей константой.Аналогично, скажем
print
, это функция, которая принимает некоторое время и выводит его на консоль. Поскольку вызовы функций не могут иметь побочных эффектов на чистом функциональном языке, мы вместо этого представляем, что это функция, которая берет временную метку и возвращает действие ее печати на консоль. Опять же, это реальная функция, потому что, если вы дадите ей одну и ту же временную метку, она будет возвращать одно и то же действие при печати.Теперь, как вы можете распечатать текущее время на консоли? Ну, вы должны объединить два действия. Так как мы можем это сделать? Мы не можем просто перейти
getClockTime
кprint
, так как печать ожидает метку времени, а не действие. Но мы можем представить, что существует оператор,>>=
который объединяет два действия: одно получает метку времени, а другое принимает аргумент и печатает его. Применение этого к ранее упомянутым действиям приводит к ... tadaaa ... новому действию, которое получает текущее время и печатает его. И это, кстати, именно так, как это делается в Haskell.Таким образом, концептуально вы можете посмотреть на это следующим образом: чисто функциональная программа не выполняет никаких операций ввода-вывода, она определяет действие , которое затем выполняет система времени выполнения. Действие одно и то же каждый раз, но результат его выполнения зависит от обстоятельств , когда оно выполняется.
Я не знаю, было ли это более понятным, чем другие объяснения, но иногда это помогает мне думать об этом таким образом.
источник
getClockTime
действие вместо функции. Хорошо, если вы вызываете так, а затем вызываете каждое действие функции , тогда даже императивное программирование станет функциональным программированием. Или, может быть, вы хотели бы назвать это активным программированием.main
действие. Это позволяет отделить чистый функциональный код от императивного кода, и это разделение обеспечивается системой типов. Рассматривая действия как объекты первого класса, вы также можете передавать их и создавать свои собственные «управляющие структуры».->
- именно так стандарт определяет термин, и это действительно единственное разумное определение в контексте Haskell. Так - то, тип которогоIO Whatever
является не функцией.putStrLn
это не действие - это функция, которая возвращает действие.getLine
переменная, которая содержит действие. Действия - это значения, переменные и функции - это «контейнеры» / «метки», которые мы даем этим действиям.Да и нет.
Различные функциональные языки программирования решают их по-разному.
В Хаскеле (очень чистом) все это должно происходить в чем-то, что называется монадой ввода / вывода - смотрите здесь .
Вы можете думать об этом как о получении другого входа (и выхода) в вашу функцию (состояние мира) или просто как о месте, где происходит «нечистота», такая как получение изменяющегося времени.
В другие языки, такие как F #, просто встроена некоторая нечистота, и поэтому вы можете иметь функцию, которая возвращает разные значения для одного и того же ввода - как обычные императивные языки.
Как отметил в своем комментарии Джеффри Бурка: Вот хорошее введение в монаду ввода / вывода прямо из вики Haskell.
источник
В Haskell каждый использует конструкцию, названную монадой, чтобы обработать побочные эффекты. Монада в основном означает, что вы инкапсулируете значения в контейнере и имеете некоторые функции для объединения функций из значений в значения внутри контейнера. Если наш контейнер имеет тип:
мы можем безопасно осуществлять действия IO. Этот тип означает: действие типа
IO
- это функция, которая принимает токен типаRealWorld
и возвращает новый токен вместе с результатом.Идея заключается в том, что каждое действие IO изменяет внешнее состояние, представленное магическим токеном
RealWorld
. Используя монады, можно связать несколько функций, которые изменяют реальный мир вместе. Наиболее важная функция монады>>=
, произносится как связать :>>=
принимает одно действие и функцию, которая берет результат этого действия и создает из него новое действие. Тип возвращаемого значения - это новое действие. Например, давайте представим, что есть функцияnow :: IO String
, которая возвращает строку, представляющую текущее время. Мы можем связать это с функцией,putStrLn
чтобы распечатать это:Или написано в
do
-Notation, которая более знакома для императивного программиста:Все это чисто, поскольку мы сопоставляем мутацию и информацию о внешнем мире с
RealWorld
токеном. Поэтому каждый раз, когда вы запускаете это действие, вы, конечно, получаете разные выходные данные, но входные данные не совпадают:RealWorld
токен отличается.источник
RealWorld
дымовой завесой. Тем не менее, наиболее важным является то, как этот предполагаемый объект передается по цепочке. Недостающий фрагмент находится там, где он начинается, где находится источник или связь с реальным миром - он начинается с главной функции, которая выполняется в монаде IO.RealWorld
объекте, который передается в программу при запуске.main
функция принимаетRealWorld
аргумент. Только после казни это передается.RealWorld
и предоставляют только незначительные функции, чтобы изменить его такputStrLn
, заключается в том, что некоторые программисты на Haskell не меняютсяRealWorld
с одной из своих программ, так что адрес и дата рождения Haskell Curry таковы, что они становятся ближайшими соседями взросление (это может повредить пространственно-временной континуум таким образом, чтобы повредить язык программирования Haskell.)RealWorld -> (a, RealWorld)
не превращается в метафору даже в условиях параллелизма, если вы помните, что реальный мир может постоянно изменяться другими частями вселенной вне вашей функции (или вашего текущего процесса). Так (а) выбирает , предзадано не ломается, и (б) каждый раз , когда значение , которое имеет ,RealWorld
как его типа передается функция, то функция должна быть повторно оценено, потому что реальный мир будет измениться в то же время ( который моделируется как объяснил @fuz, возвращая различное «значение токена» каждый раз, когда мы взаимодействуем с реальным миром).Большинство функциональных языков программирования не являются чистыми, то есть они позволяют функциям не только зависеть от их значений. В этих языках вполне возможно иметь функцию, возвращающую текущее время. Для языков, на которых вы отметили этот вопрос, это относится к Scala и F # (а также к большинству других вариантов ML ).
В таких языках, как Haskell и Clean , которые являются чистыми, ситуация иная. В Haskell текущее время будет доступно не через функцию, а через так называемое IO-действие, которое является способом Haskell для инкапсуляции побочных эффектов.
В Clean это будет функция, но функция примет мировое значение в качестве аргумента и вернет значение нового мира (в дополнение к текущему времени) в качестве результата. Система типов будет гарантировать, что каждое мировое значение может быть использовано только один раз (и каждая функция, которая потребляет мировое значение, создаст новое). Таким образом, функция времени должна вызываться каждый раз с другим аргументом, и, следовательно, каждый раз будет разрешено возвращать другое время.
источник
«Текущее время» не является функцией. Это параметр. Если ваш код зависит от текущего времени, это означает, что ваш код параметризован по времени.
источник
Это можно сделать абсолютно функционально. Есть несколько способов сделать это, но самый простой - заставить функцию времени возвращать не только время, но и функцию, которую вы должны вызвать для получения следующего измерения времени .
В C # вы можете реализовать это так:
(Имейте в виду, что это пример, который должен быть простым, а не практичным. В частности, узлы списка не могут быть собраны сборщиком мусора, потому что они укоренены в ProgramStartTime.)
Этот класс ClockStamp действует как неизменный связанный список, но на самом деле узлы генерируются по требованию, поэтому они могут содержать «текущее» время. Любая функция, которая хочет измерить время, должна иметь параметр 'clockStamp' и также должна возвращать свое последнее измерение времени в своем результате (чтобы вызывающая сторона не видела старые измерения), например так:
Конечно, немного неудобно проходить последнее измерение внутри и снаружи, внутри и снаружи, внутри и снаружи. Есть много способов скрыть шаблон, особенно на уровне языкового дизайна. Я думаю, что Хаскелл использует этот вид трюка, а затем скрывает уродливые части, используя монады.
источник
i++
цикл for не является прозрачным поstruct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }
. Но код с этим все еще не выглядел бы так же хорошо, как Haskell сdo
синтаксисом.SelectMany
, который включает синтаксис понимания запроса. Вы все еще не можете программировать полиморфно над монадами, так что все это тяжелая битва против слабой системы типов :(Я удивлен, что ни один из ответов или комментариев не упоминает коалгебры или коиндукцию. Обычно при рассуждениях о бесконечных структурах данных упоминается коиндукция, но она также применима к бесконечному потоку наблюдений, такому как регистр времени в ЦП. Коалгебра моделирует скрытое состояние; и модели соиндукции, наблюдающие это состояние. ( Построение нормальных индукционных моделей состояние.)
Это горячая тема в Реактивном функциональном программировании. Если вы заинтересованы в подобных вещах, прочитайте это: http://digitalcommons.ohsu.edu/csetech/91/ (28 стр.)
источник
Да, для чистой функции возможно вернуть время, если оно указано в качестве параметра. Другой аргумент времени, другой результат времени. Затем формируйте и другие функции времени и объединяйте их с простым словарем функций (-временных) -преобразующих (высших порядков) функций. Поскольку подход не использует состояние, время здесь может быть непрерывным (независимым от разрешения), а не дискретным, что значительно повышает модульность . Эта интуиция является основой функционально-реактивного программирования (FRP).
источник
Да! Ты прав! Now () или CurrentTime () или сигнатура любого метода такого типа не демонстрирует ссылочную прозрачность в одном отношении. Но по указанию компилятору он параметризуется входом системных часов.
По выводу Now () может выглядеть так, что не следует ссылочной прозрачности. Но фактическое поведение системных часов и функции над ними придерживается ссылочной прозрачности.
источник
Да, функция получения времени может существовать в функциональном программировании с использованием слегка модифицированной версии функционального программирования, известной как нечистое функциональное программирование (по умолчанию или основной является чисто функциональное программирование).
В случае получения времени (или чтения файла, или запуска ракеты) код должен взаимодействовать с внешним миром, чтобы выполнить работу, и этот внешний мир не основан на чистых основах функционального программирования. Чтобы позволить чисто функциональному миру программирования взаимодействовать с этим нечистым внешним миром, люди внедрили нечистое функциональное программирование. В конце концов, программное обеспечение, которое не взаимодействует с внешним миром, ничем не полезно, кроме как выполнять некоторые математические вычисления.
Немногие функциональные языки программирования имеют эту встроенную особенность нечистоты, так что нелегко выделить, какой код нечистый, а какой - чистый (например, F # и т. Д.), И некоторые функциональные языки программирования должны убедиться, что когда вы делаете нечистые вещи этот код явно выделяется по сравнению с чистым кодом, таким как Haskell.
Еще один интересный способ увидеть это состоит в том, что ваша функция get time в функциональном программировании будет принимать «мировой» объект, который имеет текущее состояние мира, такое как время, количество людей, живущих в мире, и т. Д. Затем, получая время из какого мира объект всегда будет чистым, т. е. если вы перейдете в одно и то же состояние мира, вы всегда получите одно и то же время.
источник
Ваш вопрос объединяет две взаимосвязанные меры компьютерного языка: функциональный / императивный и чистый / нечистый.
Функциональный язык определяет отношения между входами и выходами функций, а императивный язык описывает конкретные операции в определенном порядке выполнения.
Чистый язык не создает побочных эффектов и не зависит от них, а нечистый язык использует их повсюду.
На сто процентов чистые программы в основном бесполезны. Они могут выполнять интересные вычисления, но поскольку у них не может быть побочных эффектов, у них нет ввода или вывода, поэтому вы никогда не узнаете, что они рассчитали.
Чтобы быть вообще полезной, программа должна быть как минимум нечистой. Один из способов сделать чистую программу полезной - поместить ее в тонкую нечистую оболочку. Как эта непроверенная программа на Haskell:
источник
IO
ценности и результаты чистыми.Вы изучаете очень важный предмет в функциональном программировании, то есть выполнение ввода-вывода. Многие чистые языки делают это с помощью встроенных доменных языков, например, подъязыка, задачей которого является кодирование действий. , которые могут иметь результаты.
Например, среда выполнения Haskell ожидает, что я определю действие под названием
main
, которое состоит из всех действий, составляющих мою программу. Затем среда выполнения выполняет это действие. В большинстве случаев при этом выполняется чистый код. Время от времени среда выполнения использует вычисленные данные для выполнения операций ввода-вывода и возвращает данные обратно в чистый код.Вы можете жаловаться, что это звучит как мошенничество, и в некотором роде: определяя действия и ожидая, что среда выполнения их выполнит, программист может делать все, что может делать обычная программа. Но система строгого типа Хаскелла создает сильный барьер между чистыми и «нечистыми» частями программы: вы не можете просто добавить, скажем, две секунды к текущему времени ЦП, и распечатать его, вы должны определить действие, которое приведет к текущему Время процессора и передайте результат другому действию, которое добавляет две секунды и печатает результат. Однако написание слишком большой части программы считается плохим стилем, поскольку из-за этого сложно определить, какие эффекты вызваны, по сравнению с типами языка Haskell, которые сообщают нам все, что мы можем знать о значении.
Пример:
clock_t c = time(NULL); printf("%d\n", c + 2);
в C, противmain = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)
в Haskell. Оператор>>=
используется для составления действий, передавая результат первого в функцию, приводящую ко второму действию. Это выглядит довольно загадочно, компиляторы Haskell поддерживают синтаксический сахар, который позволяет нам писать последний код следующим образом:Последнее выглядит весьма императивно, не так ли?
источник
Он не существует в чисто функциональном смысле.
Вначале может быть полезно узнать, как время извлекается на компьютере. По сути, есть бортовая схема, которая отслеживает время (именно поэтому компьютеру обычно требуется небольшая батарея). Тогда может быть какой-то внутренний процесс, который устанавливает значение времени в определенном регистре памяти. Который по существу сводится к значению, которое может быть получено процессором.
Для Хаскелла существует концепция «действия ввода-вывода», которая представляет тип, который может быть выполнен для выполнения некоторого процесса ввода-вывода. Таким образом, вместо ссылки на
time
значение мы ссылаемся наIO Time
значение. Все это было бы чисто функционально. Мы не ссылаемся наtime
что-то вроде «прочитайте значение регистра времени» .Когда мы на самом деле выполняем программу на Haskell, действие IO фактически будет иметь место.
источник