Map runSTArray над списком STArrays?

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

doAll :: .. -> [ST s (STArray s (Int, Int) Int)]

Причина, по которой я не возвращаю [UArray (Int,Int) Int] напрямую, состоит в том, что doAll вызывается рекурсивно, модифицирует элементы матриц в списке и добавляет новые матрицы. Я не хочу замораживать и оттаивать матрицы без необходимости.

Пока все хорошо. Я могу проверить n -th матрицу (типа Array (Int, Int) Int) в ghci

runSTArray (matrices !! 0)
runSTArray (matrices !! 1)

и я получаю правильные результаты для моего алгоритма. Однако я не нашел способ сопоставить runSTUArray над списком, который возвращается doAll:

map (runSTArray) matrices

Couldn't match expected type `forall s. ST s (STArray s i0 e0)'
            with actual type `ST s0 (STArray s0 (Int, Int) Int)'

Такая же проблема возникает, если я пытаюсь оценить рекурсивно по списку или попытаться оценить отдельные элементы, завернутые в функцию

Может кто-нибудь объяснить, что происходит (я действительно не понял последствий ключевого слова forall) и как я мог оценить массивы в списке?

Ответ 1

Это неудачное следствие трюка типа, который делает ST безопасным. Во-первых, вам нужно знать, как работает ST. Единственный способ получить от монады ST до чистого кода - с помощью функции runST или других функций, построенных на ней, как runSTArray. Все они имеют форму forall s.. Это означает, что для построения массива из STArray компилятор должен иметь возможность определить, что он может заменить любой тип, который ему нравится, для переменной типа s внутри runST.

Теперь рассмотрим функцию map :: (a -> b) -> [a] -> [b]. Это показывает, что каждый элемент в списке должен иметь один и тот же тип (a), а значит, и тот же s. Но это дополнительное ограничение нарушает тип runSTArray, который заявляет, что компилятор должен иметь возможность свободно подставлять другие значения для s.

Вы можете обойти это, указав новую функцию, чтобы сначала заморозить массивы внутри монады ST, а затем запустить результирующее действие ST:

runSTArrays :: Ix ix => (forall s. [ST s (STArray s ix a)]) -> [Array ix a]
runSTArrays arrayList = runST $ (sequence arrayList >>= mapM freeze)

Обратите внимание, что для forall требуется расширение RankNTypes.

Ответ 2

Вы просто отскочили от ограничений системы типов.

У runSTArray есть более высокий ранжированный тип. Вы должны передать ему ST-действие, переменная типа состояния которого уникальна. Тем не менее, в Haskell, как правило, невозможно иметь такие значения в списках.

Все это умная схема, чтобы убедиться, что ценности, которые вы производите в действии ST, не могут убежать оттуда. Это значит, что ваш дизайн как-то сломан.

Одно предложение: вы не можете обрабатывать значения в другом действии ST, например

sequence [ ... your ST s (STArray s x) ...] >>= processing
  where
     processing :: [STArray s x] -> ST s (your results)