В этом посте stackoverflow приведен код на C++, который пытается имитировать связанные типы. Когда лямбда-функции захватывают среду (закрытие) с помощью захвата ссылок, код компилируется, но происходит сбой во время выполнения. Когда код изменился на захват по значению, код выполняется успешно как во время компиляции, так и во время выполнения. Как GHC фиксирует закрытие лямбда-функции по ссылке или по значению?
Haskell использует сбор мусора: wiki.haskell.org/GHC/Memory_Management
В Haskell все неизменяемо, и отличить копию от «исходного» значения невозможно. Поэтому захват по значению или по ссылке не имеет значения и может рассматриваться как деталь реализации во время выполнения. (Однако копирование больших лениво вычисляемых значений может быть очень неэффективным, поэтому на практике захваты реализуются «по ссылке»).
На семантическом уровне Haskell не «фиксирует переменные». Он просто предоставляет возможности языка, которые позволяют вам определять новые функции, которые могут использовать переменные как часть своего определения (точно так же, как let x = a + b
может определять новый Integer
x
в терминах переменных a
, b
и +
). Сделать эту работу — проблема разработчика языка Haskell. C++ не предоставляет вам этого в качестве базовой функции, поэтому, когда вам нужно что-то подобное, вы несете ответственность за понимание того, что делает среда выполнения, и правильную реализацию этого с точки зрения имеющихся у нее функций.
Если вы думаете о замыканиях, захвате переменных, о том, являются ли вещи ссылками или значениями, все это довольно сложные детали реализации в Haskell, а не то, что вы считаете основами. Это интересные детали и даже полезные, когда вам нужно действительно оптимизировать производительность. Так что никаких возражений против того, чтобы кто-то спрашивал о них. Но вам не обязательно знать их, чтобы программировать на Haskell, и если вы учитесь, я бы рекомендовал изучить базовую модель высокого уровня, а затем подготовиться к тому, как она преобразуется в концепции более низкого уровня.
GHC фиксирует переменные в замыкании по ссылке, но (1) поскольку все значения размещаются в куче, собранной мусором, ссылка остается действительной в течение всего времени существования замыкания, поэтому ошибок во время выполнения не будет, как в вашем примере на C++; и (2) поскольку значения Haskell неизменяемы, поведение программы в основном такое же, как если бы замыкание было зафиксировано по значению, поскольку значения ссылок не меняются в течение всего времени существования программ Haskell.
Так, например, в программе:
subtracter :: Integer -> (Integer -> Integer)
subtracter delta = let increment = -delta in \x -> x + increment
Результатом вызова subtracter 42
является лямбда x -> x + increment
, которая захватывает значение increment
по ссылке. Несмотря на то, что increment
была «локальной переменной» (или максимально близкой к локальной переменной в Haskell), она по-прежнему является ссылкой на неизменяемое значение -42
в куче, а не на изменяемое значение в стеке, как это было бы быть с C++. Таким образом, ссылка будет оставаться действительной в течение всего времени жизни лямбды и всегда будет неизменяемой ссылкой на значение -42
, поэтому эффект будет таким же, как если бы лямбда захватила ее «по значению» как -42
.
Первый ответ на это: вас это не должно волновать. Haskell заключается в том, чтобы выразить то, что вы хотите, в виде чего-то вроде математического выражения и позволить компилятору/среде выполнения выполнить работу по выяснению того, как наиболее эффективно вычислить это. Однако вы можете себе представить, что без оптимизации все происходит по ссылке, а не по значению. Причина, по которой у вас нет той же проблемы, что и у вашего кода на C++, заключается в том, что в Haskell есть сборщик мусора, который определяет, когда безопасно освободить что-то из памяти. В C++ нет сборщика мусора; вы должны быть осторожны с вещами, выходящими за рамки.