it-swarm.com.ru

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

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

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

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

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

Как говорит Википедия ( ссылка ),

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

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

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

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

619
Nawaz

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

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

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

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

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

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

155
dainichi

И да и нет.

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

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

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

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

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

349
Carsten

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

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

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

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

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

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

now >>= putStrLn

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

do currTime <- now
   putStrLn currTime

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

143
fuz

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

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

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

69
sepp2k

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

47
Vlad Patryshev

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

В 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
}

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

20
Craig Gidney

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

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

14
Jeffrey Aguilera

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

11
Conal

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

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

10
MduSenthil

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

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

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

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

10
Ankur

Ваш вопрос объединяет две взаимосвязанные меры компьютерного языка: функциональный/императивный и чистый/нечистый.

Функциональный язык определяет отношения между входами и выходами функций, а императивный язык описывает конкретные операции в определенном порядке выполнения.

Чистый язык не создает побочных эффектов и не зависит от них, а нечистый язык использует их повсюду.

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

Чтобы быть вообще полезной, программа должна быть как минимум нечистой. Один из способов сделать чистую программу полезной - поместить ее в тонкую нечистую оболочку. Как эта непроверенная программа на Haskell:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print
7
NovaDenizen

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

Например, среда выполнения 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 поддерживают синтаксический сахар, который позволяет нам писать последний код следующим образом:

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

Последнее выглядит весьма императивно, не так ли?

2
MauganRa

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

Он не существует в чисто функциональном смысле.

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

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


Для Haskell существует концепция "действия ввода-вывода", представляющая тип, который может быть выполнен для выполнения некоторого процесса IO. Таким образом, вместо ссылки на значение time мы ссылаемся на значение IO Time. Все это было бы чисто функционально. Мы ссылаемся не на time, а на что-то вроде 'прочитать значение регистра времени'.

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

1
Chris Stryczynski