Как может существовать функция времени в функциональном программировании? - PullRequest
621 голосов
/ 01 сентября 2011

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

Например, рассмотрим это:

f(x,y) = x*x + y; // It is a mathematical function

Неважносколько раз вы используете f(10,4), его значение всегда будет 104.Таким образом, где бы вы ни написали f(10,4), вы можете заменить его на 104, не изменяя значения всего выражения.Это свойство называется ссылочной прозрачностью выражения.

Как сказано в Википедии ( link ),

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

Может ли функция времени (которая возвращает ток время) существовать в функциональном программировании?

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

  • Или, если нет, то как можно узнатьтекущее время в функциональном программировании?

Ответы [ 13 ]

349 голосов
/ 01 сентября 2011

Да и нет.

Различные функциональные языки программирования решают их по-разному.

В Haskell (очень чистом) все это должно происходить во что-то, называемое I /О Монада - см. здесь .

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

В других языках, таких как F #, просто встроена некоторая нечистота, и поэтому вы можете иметь функцию, которая возвращает разные значения для одного и того же ввода - точно так же, как normal императивные языки.

Как отметил Джеффри Бурка в своем комментарии: Вот замечательное введение в монаду ввода / вывода прямо из вики Haskell.

157 голосов
/ 01 сентября 2011

Другой способ объяснить это так: никакая функция не может получить текущее время (поскольку оно постоянно меняется), но действие может получить текущее время.Скажем, getClockTime - это константа (или нулевая функция, если хотите), которая представляет действие получения текущего времени.Это действие одинаково каждый раз, независимо от того, когда оно используется, поэтому оно является реальной константой.

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

Теперь, как вы можете напечатать текущее времяна консоль?Ну, вы должны объединить два действия.Так как мы можем это сделать?Мы не можем просто передать getClockTime в print, так как печать ожидает метку времени, а не действие.Но мы можем представить, что существует оператор >>=, который объединяет два действия, одно из которых получает временную метку, а другое принимает одно в качестве аргумента и печатает его.Применение этого к ранее упомянутым действиям приводит к ... tadaaa ... новому действию, которое получает текущее время и печатает его.И это, кстати, именно так, как это делается в Haskell.

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

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

Я не знаю, было ли это более ясным, чем другие объяснения, но иногда это помогает мне думать об этом таким образом.

143 голосов
/ 01 сентября 2011

В Haskell используется конструкция под названием monad для обработки побочных эффектов.Монада в основном означает, что вы инкапсулируете значения в контейнере и имеете некоторые функции для объединения функций из значений в значения внутри контейнера.Если наш контейнер имеет тип:

data IO a = IO (RealWorld -> (a,RealWorld))

, мы можем безопасно выполнять действия ввода-вывода.Этот тип означает: действие типа IO является функцией, которая берет токен типа RealWorld и возвращает новый токен вместе с результатом.

Идея заключается в том, что каждое действие ввода-выводамутирует внешнее состояние, представленное магическим токеном RealWorld.Используя монады, можно связать несколько функций, которые изменяют реальный мир вместе.Наиболее важной функцией монады является >>=, произносится bind :

(>>=) :: IO a -> (a -> IO b) -> IO b

>>= принимает одно действие и функцию, которая принимает результат этого действия и создает новыйдействие из этого.Тип возвращаемого значения - это новое действие.Например, давайте представим, что есть функция now :: IO String, которая возвращает строку, представляющую текущее время.Мы можем связать его с помощью функции putStrLn, чтобы распечатать его:

now >>= putStrLn

Или записать в do -Notation, которая более знакома императивному программисту:

do currTime <- now
   putStrLn currTime

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

69 голосов
/ 01 сентября 2011

Большинство функциональных языков программирования не являются чистыми, то есть они позволяют функциям не только зависеть от их значений.В этих языках вполне возможно иметь функцию, возвращающую текущее время.Из языков, на которых вы отметили этот вопрос, это относится к Scala и F # (а также к большинству других вариантов ML ).

Inтакие языки, как Haskell и Clean , которые являются чистыми, ситуация иная.В Haskell текущее время будет доступно не через функцию, а через так называемое IO-действие, которое является способом инкапсуляции побочных эффектов в Haskell.

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

48 голосов
/ 01 сентября 2011

«Текущее время» не является функцией.Это параметр.Если ваш код зависит от текущего времени, это означает, что ваш код параметризован по времени.

20 голосов
/ 13 июня 2012

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

В C #Вы могли бы реализовать это так:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(Имейте в виду, что это пример, который должен быть простым, а не практичным. В частности, узлы списка не могут быть собраны сборщиком мусора, потому что они имеют корниProgramStartTime.)

Этот класс ClockStamp действует как неизменяемый связанный список, но на самом деле узлы генерируются по требованию, поэтому они могут содержать «текущее» время.Любая функция, которая хочет измерить время, должна иметь параметр 'clockStamp' и также должна возвращать свое последнее измерение времени в своем результате (чтобы вызывающая сторона не видела старые измерения), например:

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

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

15 голосов
/ 19 сентября 2014

Я удивлен, что ни один из ответов или комментариев не упоминает коалгебры или коиндукцию. Обычно при рассуждениях о бесконечных структурах данных упоминается коиндукция, но она также применима к бесконечному потоку наблюдений, такому как регистр времени в ЦП. Коалгебра моделирует скрытое состояние; и модели совместного наблюдения *, наблюдающие это состояние. (Обычные индукционные модели , строящие состояние.)

Это горячая тема в Реактивном функциональном программировании. Если вы заинтересованы в подобных вещах, прочитайте это: http://digitalcommons.ohsu.edu/csetech/91/ (28 стр.)

11 голосов
/ 25 февраля 2014

Да, для чистой функции можно вернуть время, если оно указано в качестве параметра.Другой аргумент времени, другой результат времени.Затем формируйте и другие функции времени и объединяйте их с простым словарем функций (-временных) -преобразующих (высших порядков) функций.Поскольку подход не использует состояние, время здесь может быть непрерывным (не зависящим от разрешения), а не дискретным, что значительно повышает модульность .Эта интуиция является основой функционально-реактивного программирования (FRP).

10 голосов
/ 01 сентября 2011

Да!Ты прав!Now () или CurrentTime () или сигнатура любого метода такого типа не демонстрирует ссылочную прозрачность в одном отношении.Но в соответствии с инструкцией к компилятору она параметризуется входом системных часов.

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

10 голосов
/ 01 сентября 2011

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

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

Немногие функциональные языки программирования имеют такую ​​встроенную особенность нечистоты, что нелегко выделить, какой код является нечистым, а какой - чистым (например, F # и т. Д.), И некоторые функциональные языки программирования должны убедиться, что когда вы делаете некоторые нечистые вещи, которые явно выделяют код по сравнению с чистым кодом, такие как Haskell.

Еще один интересный способ увидеть это состоит в том, что ваша функция get time в функциональном программировании будет принимать объект «мир», который имеет текущее состояние мира, такое как время, количество людей, живущих в мире, и т. Д. Затем, получая время из какого объекта мира он всегда будет чистым, т. е. вы перейдете в одно и то же состояние мира, вы всегда получите одно и то же время.

...