понимание ссылочной прозрачности - PullRequest
6 голосов
/ 17 июля 2010

Обычно у меня болит голова, потому что с моими рассуждениями что-то не так:

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

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

  3. что делает логику таких функций комбинационной (в отличие от последовательной)

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

Последнее утверждение вытекает из этих рассуждений, , но это, очевидно, неверно;это означает, что в рассуждениях есть ошибка. [вопрос: где ошибка в этом рассуждении?]

UPD2.Вы, ребята, говорите много интересного, но не отвечаете на мой вопрос.Я определил это более явно сейчас.Извините за неправильное определение вопроса!

Ответы [ 5 ]

5 голосов
/ 17 июля 2010

Вопрос: где ошибка в этом рассуждении?

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

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

3 голосов
/ 17 июля 2010

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

Я думаю, что более краткий способ сформулировать вопрос может быть следующим: может ли чисто функциональный язык вычислить все, что может сделать императив?

Прежде всего, предположим, что вы выбрали императивный язык, такой как C, и сделали так, чтобы вы могли 'изменить переменные после их определения.Например:

int i;

for (i = 0;  // okay, that's one assignment
     i < 10; // just looking, that's all
     i++)    // BUZZZ!  Sorry, can't do that!

Ну вот и пошла ваша for петля.Разве мы можем сохранить наш while цикл?

while (i < 10)

Конечно, но это не очень полезно.i не может измениться, поэтому он либо будет работать вечно, либо не будет работать вообще.

Как насчет рекурсии?Да, вы можете сохранить рекурсию, и это все еще очень полезно:

int sum(int *items, unsigned int count)
{
    if (count) {
        // count the first item and sum the rest
        return *items + sum(items + 1, count - 1);
    } else {
        // no items
        return 0;
    }
}

Теперь, с помощью функций, мы не изменяем состояние, но переменные могут, ну, в общем, изменяться.Как только переменная попадает в нашу функцию, она блокируется. Однако мы можем снова вызвать функцию (рекурсия), и это похоже на получение нового набора переменных (старые остаются прежними).Хотя существует несколько экземпляров items и count, sum((int[]){1,2,3}, 3) всегда будет иметь значение 6, поэтому вы можете заменить это выражение на 6, если хотите.

Можем ли мы что-нибудь еще сделать?мы хотим?Я не уверен на 100%, но я думаю, что ответ «да».Вы, конечно, можете, если у вас есть закрытие.


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

Я рекомендую изучить Haskell, чисто функциональный язык.На Хаскеле, строго говоря, нет оператора «присваивания».Например:

my_sum numbers = ??? where
    i     = 0
    total = 0

Здесь вы не можете написать цикл for, который увеличивает i и total по мере продвижения.Однако еще не все потеряно.Просто используйте рекурсию, чтобы получать новые i s и total s:

my_sum numbers = f 0 0 where
    f i total =
        if i < length numbers
            then f i' total'
            else total
        where
            i' = i+1
            total' = total + (numbers !! i)

(Обратите внимание, что это глупый способ суммировать список в Haskell, но он демонстрирует методсправиться с одним заданием.)

Теперь рассмотрим этот крайне императивный код:

main = do
    a <- readLn
    b <- readLn
    print (a + b)

Это на самом деле синтаксический сахар для:

main =
    readLn >>= (\a ->
    readLn >>= (\b ->
    print (a + b)))

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

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

Для пояснения рассмотрим readLn.readLn - это действие, которое в случае выполнения считывает строку из стандартного ввода и выдает ее проанализированное значение.Чтобы что-то сделать с этим значением, мы не можем сохранить его в переменной, потому что это нарушит ссылочную прозрачность :

a = readLn

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

Вместо этого мы привязываем действие readLn к функции, которая имеет дело с действием, получая новое действие,вот так:

readLn >>= (\x -> print (x + 1))

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

2 голосов
/ 17 июля 2010

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

Язык, который вы могли бы проверить, чтобы узнать, как все делается на чисто функциональном языке, был бы Haskell .Существуют способы использования «обновляемых возможностей хранения», например, Reader Monad и State Monad .Если вас интересуют чисто функциональные структуры данных, Okasaki может быть хорошим чтением.

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

Я действительно не знаю о вопросе таблицы истинности.

1 голос
/ 17 июля 2010

Вот мой ответ на вопрос:

Любая система может быть описана как комбинаторная функция, большая или маленькая.

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

Вы можете даже описать, скажем, работу игрового движка как таблицу истинности или комбинаторную функцию.

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

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

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

0 голосов
/ 17 июля 2010

Ошибка в Ваших рассуждениях следующая:
«это означает, что такая функция может быть представлена ​​в виде таблицы истинности».

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

Поэтому функция не равна логическому вентилю, а скорее плану построения такого логического вентиля в зависимости от фактического (во время выполнения определенного) ввода!

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

...