Побочный эффект — это все, что позволяет вам наблюдать разницу в поведении программы в зависимости от того, было ли, сколько раз или в каком порядке оценивается выражение или выполняется действие, то есть нарушается прозрачность ссылок. Изменение переменных является одним из примеров побочного эффекта, как и отправка сообщения по параллельному каналу, печать на терминал, запись в файл или чтение из сети.
Именно наблюдаемость внутри обычного безопасного кода делает что-то побочным; Среда выполнения Haskell все время использует изменяемые переменные для ленивых вычислений, но вы не можете увидеть это изнутри языка без небезопасного кода. Если возможно наблюдать эффект относительно контекста, в котором вы находитесь, это все равно побочный эффект. Итак, то, что вы описываете (ограничение тех, кто может изменять поля объекта), звучит, может быть, безопаснее, но оно не лишено побочных эффектов.
Например, Debug.Trace.trace :: String -> a -> a
имеет побочный эффект при оценке, потому что trace "x" (1 :: Int) + trace "x" (1 :: Int)
заметно отличается от let x = trace "x" (1 :: Int) in x + x
:
> trace "x" (1 :: Int) + trace "x" (1 :: Int)
x
x
2
> let x = trace "x" (1 :: Int) in x + x
x
2
modifyIORef :: IORef a -> (a -> a) -> IO ()
имеет побочный эффект при выполнении, поскольку многократное изменение изменяемой ссылки явно отличается от ее однократного изменения:
increment :: IORef Int -> IO ()
increment r = modifyIORef r (+ 1)
main :: IO ()
main = do
r1 <- newIORef 0
increment r1
print =<< readIORef r1 -- 1
r2 <- newIORef 0
increment r2
increment r2
print =<< readIORef r2 -- 2
(Но обратите внимание, что значение типа IO a
для некоторых a
является чистым при вычислении: это не значение типа a
, «помеченное» тегом тот факт, что это произошло из ввода-вывода; скорее, это программа или действие, которое возвращает значение типа a
при подключении к main
и выполняется средой выполнения.)
Обратите внимание, что не весь эффективный код является побочным: pure () :: IO ()
находится в IO
, но явно не имеет побочных эффектов. Аналогично, ST
предоставляет локальные изменяемые переменные, которые гарантированно не исчезнут и не будут видны за пределами своей области видимости, поэтому вы можете реализовать чистую функцию, которая нечиста внутри:
pureSum :: Int -> Int
pureSum n = sum [1 .. n]
impureSum :: Int -> IO Int
impureSum n = do
result <- newIORef 0
for_ [1 .. n] $ \ x -> do
putStrLn ("Adding " ++ show x) -- Side effect!
modifyIORef result (+ x)
readIORef result
internallyImpureSum :: Int -> Int
internallyImpureSum = runST $ do
result <- newSTRef 0
for_ [1 .. n] $ \ x -> do
-- Can’t perform any side effects observable outside.
modifySTRef result (+ x)
-- Can *read* the reference, but returning
-- the reference ‘result’ itself would be
-- a type error.
readSTRef result
Что касается «установки набора переменных при инициализации объекта», то в основном это шаблон, используемый в Haskell не только для обеспечения безопасности, но и как основанная на математике философия моделирования данных в целом.
В языках ООП соглашение о моделировании изменяющегося состояния заключается в создании единого объекта, который имеет понятие идентичности, и изменении его с течением времени с помощью команд или прямого изменения. Ожидается, что объект останется действительным, поддерживая все свои инварианты при каждом изменении состояния.
В то время как в Haskell принято, что объект является неизменяемым снимком или представлением состояния, и вы можете смоделировать изменяющееся состояние, просто создав новое значение для представления новое состояние. Если вам больше не нужен старый, просто забудьте о нем и дайте ему быть собранным мусором. Объекту не нужно поддерживать какие-либо инварианты после создания, потому что он неизменяем: ему просто нужно принудительно применять инварианты один раз при создании. Это можно сделать с помощью точного моделирования данных с помощью алгебраических типов данных (т. н. «сделать недопустимые состояния непредставимыми») или с помощью функций инкапсуляции и интеллектуального конструктора для предотвращения создания недопустимых значений (т. н. «правильность путем построения»).
person
Jon Purdy
schedule
03.09.2020