Что означает совместное использование в реализации функционального языка программирования

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

let x = sin x in x * x

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


person user1850254    schedule 07.05.2013    source источник
comment
Кэширование данных для повторного использования скорее называется запоминание, а не совместное использование. Если у вас есть древовидные данные, содержащие одно и то же поддерево несколько раз, то преобразование их в ориентированный ациклический граф (DAG) для совместного использования общего поддерева; это то, что я бы назвал обменом. Кроме того, вопрос (из-за его открытого характера) не совсем соответствует формату SO. Не могли бы вы конкретизировать, привести примеры,...   -  person chris    schedule 08.05.2013


Ответы (3)


Самый яркий пример совместного использования в функциональном программировании — это Clean, основанный на переписывании графов. Там вычисление относится к DAG, поэтому мы можем рассматривать выражение (sin x) * (sin x) как

    (*)
  /     \
sin x   sin x

Системы перезаписи графов имеют явное понятие совместного использования, поэтому мы могли бы выразить это вычисление как

   (*)
   / \
   \ /
  sin x

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

f x = (sin x) * (sin x)

в

f x = sinx * sinx
  where sinx = sin x

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

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

(string-append (read-line)
               (read-line))

а также

(let ((s (read-line)))
  (string-append s s))

Первый дважды выполняет действие ввода-вывода, запрашивая у пользователя две строки и добавляя их; второй «разделяет» действие ввода-вывода, выполняя его один раз и добавляя к себе. В общем, совместное использование чистых вычислений сокращает время выполнения без изменения результата программы, в то время как совместное использование побочного значения (которое может меняться со временем или взаимодействовать с пользователем) изменяет результат. Чтобы компилятор автоматически разделял вычисления, он должен знать, что они чистые, и, таким образом, сокращение числа вычислений не имеет значения. Clean делает это с уникальными типами; действие ввода-вывода имеет атрибут типа UNQ, который сообщает компилятору, что его нельзя использовать совместно. Haskell делает то же самое несколько иначе с монадой IO.

person isturdy    schedule 08.05.2013
comment
Действительно, в вашем примере where sinx = sin x нет причин вводить совместное использование. Чистота гарантирует, что независимо от того, равны ли два перемножаемых значения указателю, окончательное значение выражения будет одинаковым. Теперь очевидной оптимизацией является совместное использование этого значения, и GHC иногда позволяет подняться вверх, чтобы представить это совместное использование. Тем не менее, мы также можем вычислить sin x, скопировать его в два адреса, а затем умножить, если захотим. Совместное использование незаметно в семантике Haskell. StableName - это самое близкое, что вы можете получить, не привязывая себя к деталям реализации. - person J. Abrahamson; 08.05.2013
comment
Верно (и теперь отредактировано). Я видел, что в целом такое предложение where вводит совместное использование в GHC, но обратной стороной возможности прозрачного совместного использования значений является возможность прозрачного отмены совместного использования (встраивание sin x). - person isturdy; 08.05.2013
comment
Да, согласен, GHC, как правило, хорошо справляется с поиском возможностей для обмена. let и where, кажется, гарантируют это. - person J. Abrahamson; 08.05.2013

Совместное использование — это своего рода равенство: равенство указателей. В области ценностей Haskell (семантическая интерпретация) нет такой вещи, как совместное использование. Значения могут быть равны, только если они имеют экземпляры Eq, и тогда "равенство" определяется как бинарное отношение (==).

Таким образом, совместное использование избегает семантической интерпретации, ссылаясь на это лежащее в основе равенство указателей, которое существует благодаря реализации, а не семантике. Хотя иногда это полезный побочный эффект. К сожалению, поскольку язык Haskell определяется его семантической интерпретацией, использование совместного использования является неопределенным поведением. Он привязан к конкретной реализации. Несколько библиотек используют совместное использование и, таким образом, их поведение связано с GHC.

Однако есть один встроенный механизм обмена. Об этом свидетельствует StableName интерфейс. Мы генерируем объекты StableName a, используя makeStableName :: a -> IO (StableName a), и имеем instance Eq (StableName a) — таким образом, StableName вводит своего рода равенство для любого типа.

StableName равенство — это почти равенство указателей. Чтобы процитировать документацию Хэддока

Если sn1 :: StableName и sn2 :: StableName и sn1 == sn2, то sn1 и sn2 были созданы вызовами makeStableName для одного и того же объекта.

Обратите внимание, что это всего лишь оператор if, а не если и только если. Тот факт, что две вещи могут быть «эквивалентны указателям», но все же иногда иметь разные стабильные имена, является (а) вынужденным из-за гибкости, которую Haskell оставляет сборщику мусора, и (б) лазейкой, которая позволяет StableNames существовать в любой реализации Haskell, даже если нет такая вещь, как «равенство указателя» в реализации вообще.

Эти StableName по-прежнему не имеют семантического значения, но, поскольку они представлены в монаде IO, все в порядке. Вместо этого можно сказать, что они реализуют (по иронии судьбы) нестабильное подмножество наименьшего (наиболее конкретного) отношения равенства, возможного для любого типа.

person J. Abrahamson    schedule 08.05.2013

Ваш пример не является примером совместного использования - он просто умножает значение на себя (а затем отбрасывает исходное значение).

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

p  = (1, 2)
pp = (p, p)  -- p is shared between the first and second component of pp

xs = [1, 2, 3]
ys = 0::1::xs
zs = 5::xs  -- ys and zs share the same tail

В памяти такое совместное использование приведет к структуре DAG.

person Andreas Rossberg    schedule 08.05.2013