Как " seq " Haskell отличается от других функций?
Я запутался в описании того, как Хаскелл seq
работает в учебник я читаю.
в учебнике говорится, что
вычисление выражения
seq x y
сначала оценитьx
в WHNF и только затем продолжить оценкуy
но ранее тот же учебник, объясняя, как работает ленивая оценка Haskell в целом, утверждает, что при оценке функции "оценены, но только по мере необходимости", что означает, что его
аргументы будут оцениваться слева направо, пока их верхние узлы являются конструкторами, которые соответствуют шаблону. Если шаблон является простой переменной, то аргумент не оценивается; если шаблон является конструктором, это означает оценку WHNF.
это описание оценки функции в целом, похоже, ничем не отличается от описания, приведенного для seq
. Оба - в моем начальное чтение - просто сведите их первый аргумент к WHNF.
это правильно? Как seq
отличается-особенно в том, как он обрабатывает свой первый аргумент - от любой другой функции Haskell?
2 ответов
без seq
, evaluate
, закономерности взрыва и т. д. следующее правило:
вся оценка управляется сразу соответствием картины, оценкой
if
условия или примитивные числовые операции.
как оказалось, мы можем немного прищуриться и сделать все из них выглядят как сопоставление с образцом:
if c then x else y
=
case c of
True -> x
False -> y
x > y = case x of
0 -> case y of ...
0 + y = y
1 + 1 = 2
etc. В конечном счете, вещь, оцениваемая в любое время, является следующим примитивом IO
действие программа будет принимать, и все остальное просто рекурсивно управляется сопоставлением шаблонов.
бизнес слева направо означает, что, например,
foo False 'd' = ...
foo True _ = ...
эквивалентно
foo x y = case x of
False -> case y of
'd' -> ...
True -> ...
если foo
применяется True
и какое-то другое значение, оно не беспокоит форсирование этого значения, потому что сначала проверяет левый шаблон.
seq
, при применении к данным, действует так же, как глупо case
. Если x :: Bool
, тогда
x `seq` y = case x of
True -> y
False -> y
но seq
может, довольно теневым образом, применяться и к функциям, или к вещам неизвестного типа. Он предлагает способ оценки вещей помимо обычная цепь шаблону.
в первые дни Хаскелл, seq
был методом Seq
класс, и это имело смысл. К сожалению, разработчики сочли раздражающим иметь дело с этим классом, когда было проще просто "обмануть" и сделать seq
работать за все. Поэтому они мошенничали, и с тех пор некоторые аспекты анализа и преобразования программ стали сложнее.
seq
оценивает свой первый аргумент немедленно, не только когда он необходим - это совсем не то же самое, что общая оценка функции.
let x = 1+1
y = 2+2
in seq x (x, y)
немедленно оценивает выражение 1+1
а не 2+2
хотя ни один из них не должен быть оценен немедленно. Образно, что возвращается
(2, 2+2)
не (1+1, 2+2)
.
это иногда полезно, если вместо 1+1
у вас есть что-то вроде 1+2+3+...+1000000
что является относительно дешевым вычислением, но его недооцененная, ленивая форма очень длинная и занимает много памяти, которая, если выражение не оценивается достаточно быстро, эффективно начнет утечку памяти; эта ситуация называется место утечки в терминологии Хаскелл.
EDIT:
чтобы обратиться к вашему комментарию, вот фиктивный сценарий, в котором вложенная структура данных соответствует шаблону различная глубина. Структура данных была вперемежку с trace
вызовы на каждом уровне, чтобы вы могли контролировать, как он оценивается:
import Debug.Trace (trace)
data Age = Age Int
data Name = Name String
data Person = Person Name Age
name = trace "!NAME!" $ Name $ trace "!NAME CONTENT!" $ "John " ++ "Doe"
age = trace "!AGE!" $ Age $ trace "!AGE CONTENT!" $ 10 + 18
person = trace "!PERSON!" $ Person name age
-- person = trace "!PERSON!" $ Person (trace "!n!" name) (trace "!a!" age)
main = do
case person of p -> print "hello"
putStrLn "---"
case person of Person name age -> print "hello"
putStrLn "---"
case person of Person (Name str) age -> print "hello"
putStrLn "---"
case person of Person (Name str) (Age i) -> print "hello"
putStrLn "---"
case person of Person (Name str) (Age i) -> putStrLn $ "hello: " ++ str
putStrLn "---"
case person of Person (Name str) (Age i) -> putStrLn $ "hello: " ++ show (str, i)
выход:
"hello"
---
!PERSON!
"hello"
---
!NAME!
"hello"
---
!AGE!
"hello"
---
hello: !NAME CONTENT!
John Doe
---
hello: ("John Doe",!AGE CONTENT!
28)
обратите внимание, что выход из trace
вызовы "мешает" с выходом из putStrLn
/print
вызовы, но это на самом деле демонстрирует довольно хорошо, как оценка происходит во время выполнения.
далее, если вы определить Name
и Age
используя newtype
вместо data
оценка будет немного отличаться как newtype
значения не имеют оболочки времени выполнения, поэтому представление памяти времени выполнения person
будет один "уровень" тоньше:
newtype Age = Age Int
newtype Name = Name String
data Person = Person Name Age
"hello"
---
!PERSON!
"hello"
---
"hello"
---
"hello"
---
hello: !NAME!
!NAME CONTENT!
John Doe
---
hello: ("John Doe",!AGE!
!AGE CONTENT!
28)