Программа Haskell прерывается с помощью "loop", но я думаю, что это не должно

У меня есть этот код Haskell, который при компиляции с GHC и выполняется, прерывается с обнаруженным циклом.

data Foo = Foo ()
 deriving (Eq,Show)

type Foop = Foo -> ((),Foo)

noOp :: Foop
noOp st = ((),st)

someOp :: Foop
someOp [email protected](Foo x) = ((),st)

(<+>) :: Foop -> Foop -> Foop
(<+>) f g st = let ((_,st'),(_,st'')) = ((f st),(g st')) in ((),st'')

main = print $ (noOp <+> someOp) $ Foo ()

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

  • изменить data Foo на newtype Foo
  • изменить (noOp <+> someOp) на (someOp <+> noOp)
  • удалить деконструкцию @(Foo x)

Является ли это ошибкой в ​​GHC или это мое отсутствие понимания процесса оценки?

Ответы

Ответ 1

  • Соответствие шаблону (_, _) требует WHNF (f st, g st'). Легко.
  • Соответствие шаблону (_, (_,_)) требует WHNF g st'. Здесь проблема, потому что g является строгой, поэтому сначала ей нужно st' в WHNF. Время выполнения находит st' в шаблоне ((_,st'), (_,_)), поэтому, прежде чем он может перейти в st', ему нужно WHNF обоих кортежей. Поскольку g является строгим, для этого сначала требуется st'... и т.д.

Если вы сопоставляете результат g с ленивым неопровержимым рисунком

(<+>) f g st = let ((_,st'), ~(_,st'')) = (f st, g st') in ((),st'')

тогда проблема исчезает, потому что это оценивается таким образом:

  • Соответствие шаблону (_, _) требует WHNF (f st, g st'). Легко.
  • Соответствие шаблону (_, ~(_,_)) пока не требует ничего более.
  • Соответствие шаблону ((_,st'), ~(_,_)) требует WHNF f st. К счастью, мы можем это выполнить, потому что st не зависит от шаблона.
  • Теперь мы выполнили соответствие шаблону, время выполнения уже может быть продолжено с помощью in ((),st''). Только на этом этапе создается неопровержимый шаблон ~(_,st''), но теперь это уже не проблема, потому что st' доступен здесь, так что это просто вопрос вычисления g один раз.

Исправления, которые вы испробовали, составляют g нестрогий:

удалите деконструкцию @(Foo _)

Без этого g на самом деле не нужно искать свой аргумент для построения скелета результата, т.е. соответствие набора (_,st'') может быть выполнено без предварительного запроса WHNF st'.

измените data Foo на newtype Foo

Это приводит к тому, что конструктор Foo фактически не существует во время выполнения, поэтому нет ничего, что заставит шаблон [email protected](Foo _).

измените noOp <+> someOp на someOp <+> noOp

Как я уже говорил, цикл возникает только потому, что g является строгим. Если вы поместите f в свое положение, которое не является строгим, тогда проблем нет.

Ответ 2

Это не ошибка - вы просто нашли сложный угол семантики ленивого шаблона. Позвольте мне представить более простой случай:

> data Id a = Id a
> let Id a = undefined ; Id b = Id 3 in b
3
> let (Id a, Id b) = (undefined, Id 3) in b
*** Exception: Prelude.undefined

Разница в том, что первая let эквивалентна

case undefined of
  ~(Id a) -> case Id 3  of
               ~(Id b) -> b

а второй -

case (undefined, Id 3) of
  ~(Id a, Id b) -> b

Первый не будет оценивать undefined, если не требуется a (чего не бывает).

Второй шаблон будет сопоставляться как с шаблонами Id a, так и Id b, как только требуется переменная, заставляя в этом процессе undefined и Id 3.

Обратите внимание, что из-за этой проблемы шаблоны ~(K1 (K2 x) (K3 y)), ~(K1 ~(K2 x) (K3 y)), ~(K1 (K2 x) ~(K3 y)) и ~(K1 ~(K2 x) ~(K3 y)) имеют разную семантику.

Чтобы исправить свой код, попробуйте

let (~(_,st'),~(_,st'')) = ((f st),(g st')) in ((),st'')

или

let (_,st') = f st ; (_,st'') = g st' in ((),st'')