Данные Purescript.Складывающийся для _ разве стек не безопасен?

#purescript

Вопрос:

Если actions это достаточно большой массив, это создает переполнение стека:

 modifyPerIndex :: forall a. Array (Tuple Int (a -> a)) -> Array a -> Array a
modifyPerIndex actions array = run do
  mutableArray <- thaw array
  for_ actions (Tuple index action) -> modify index action mutableArray
  pure mutableArray
 

Это не означает:

 modifyPerIndex :: forall a. Array (Tuple Int (a -> a)) -> Array a -> Array a
modifyPerIndex actions array = run do
  mutableArray <- thaw array
  foreach actions (Tuple index action) -> void $ modify index action mutableArray
  pure mutableArray
 

Я предполагаю, что именно поэтому Контроль.Монада.ST имеет свою собственную версию foreach. for_ «s Applicative amp; Foldable » — гораздо более слабое ограничение, чем foreach «s Array «. Тем не менее, я не уверен, чего Applicative amp; Foldable не хватает, что for_ не может быть безопасным для стека (или не может быть без какого-либо другого недостатка)

Я немного покопался в источнике и отметил, что for_ это реализовано с помощью foldr . Я не уверен, где найти экземпляр папки массива.

Я довольно новичок во многом из этого, просто пытаюсь расширить свое общее понимание. 🙂

Ответ №1:

Foldable Экземпляр для Array находится здесь, и вот фактический код для foldr . Как вы можете видеть, он безопасен для стека, потому что он вообще не использует стек: это просто обычный старый цикл JS, который изменяет аккумулятор.

Что в конечном итоге оказывается небезопасным для стека traverse_ (что for_ связано с перевернутыми аргументами). Проверьте источник: видите, что функция складывания есть (*>) f ? Это означает, что результатом выполнения traverse_ некоторых Foldable из них будет серия *> вызовов, что-то вроде этого:

 traverse_ f [1,2,3,4] == f 1 *> f 2 *> f 3 *> f 4 *> pure unit
 

Ключевым моментом здесь является то, что f вычисления на самом деле не выполняются во traverse_ время выполнения. traverse_ просто строит цепочку из них таким образом, и только когда вы идете и bind это (либо с >>= a, либо внутри a do ) — вот когда эта цепочка выполняется.

И что происходит, когда вы пытаетесь запустить вычисление, построенное на *> ? Ну, *> это псевдоним для applySecond , который сам использует <*> — псевдоним для apply , который является методом Apply , чей экземпляр для ST использования ap , который, в свою очередь, опирается на монадическую привязку.

Тело ap привязывает первое вычисление, которое есть f 1 *> f 2 *> f 3 *> f 4 , но это не конечный вызов, поэтому оно отправляется в стек. В свою очередь, привязка этого вычисления приводит к попытке привязать его собственную левую часть, которая f 1 *> f 2 *> f 3 и так далее, вплоть до f 1 . Так что стопка взрывается.

traverse_ for_ ) на самом деле не может сделать ничего лучше, потому что они оба чистые, поэтому они не могут запускать эффекты, поэтому все, что они могут сделать, это создать цепочку из них в надежде, что кто-то другой выполнит ее позже.

И в этом заключается ответ: почему бы специальной версии traverse_ не знать, как запускать эффекты?

И вот: Traversable !

Это класс типа , несколько похожий на Foldable , но со встроенными эффектами. В принципе, он может складываться по последовательности, но функция складывания эффективна.

Это позволяет запускать серию эффектов, не разрушая стек, но есть и другая проблема: вы не можете игнорировать конечный результат. Если у вас есть массив со 100 тысячами элементов для начала, в конце вы получите еще один. К счастью, это не такая большая проблема, как взорвать стопку.

Комментарии:

1. Спасибо за ответ и ссылки. Теперь это имеет для меня смысл в общих чертах. Я углублюсь в это немного подробнее, чтобы убедиться, что я действительно понимаю 🙂