О скоупах

Posted on August 2, 2015 with tags: haskell, заправка ракеты, монады.

А вот ещё одна хитрая задачка. Положим у нас есть несколько устройств, которые мы можем как-то дёргать через DevHnd a (где a — тип-параметр характеризующий устройство). Проблема в том, что если два треда будут рулить устройством одновременно, то результат будет трудно предсказать. Хорошо, вводим концепцию владения устройством: управлять устройством1 можно только завладев им, если кто-то другой завладел устройством до тебя, то ты ждёшь пока устройство освободится. Просто, но есть пара нюансов:

В общем случае, когда устройства захватываются и высвобождаются в произвольный момент времени ни первую, ни вторую проблему мы вероятнее всего не решим. Но если рассмотреть ситуацию когда все устройства захватываются и освобождаются одновременно ситуация перестаёт быть такой удручающей и внезапно укладывается в традиционный для haskell’я паттерн:

    withSomething :: DevHnd a -> DevHnd b -> IO c -> IO c
    withSomething dev1 dev2 action = do
        alloc dev1 dev2
        v <- action
        dealloc dev1 dev2
        return v

Способов заблокировать несколько сущностей одновременно избежав дедлоков более одного, выберите по вкусу. Но action всё равно может содержать действия с устройствами, которые мы не захватили. Чтобы этого избежать можно сделать следующее:

    withSomething :: DevHnd a -> DevHnd b -> (forall s. StaticHnd s a -> forall s. StaticHnd s b -> Scope c) -> IO c
    withSomething dev1 dev2 action = do
        alloc dev1 dev2
        v <- action (toStatic dev1) (toStatic dev2)
        dealloc dev1 dev2
        return v

Всё. Теперь следим за руками:

Из всего этого следует что любая попытка использовать внутри нашего скоупа не захваченное устройство или устройство захваченное в другой скоуп (кроме родительского :^)) завершится ошибкой компиляции. Хорошо? Очень хорошо. Но на этом хорошее заканчивается и начинается Real World.

Приведённый код определён для двух устройств захватываемых в скоуп. Очевидным образом это довольно частный случай и этого мало. Также очевидно что функция withSomething содержит детали приватной реализации и должна быть чёрным ящиком. Есть несколько вариантов решения:

    action = accure $
        withSomething hnd1 $ \sHnd1 ->
            withSomething hnd2 $ \sHnd2 ->
                withSomething hnd3 $ \sHnd3 -> run $ do
                    doSomething sHnd1
                    doSomething sHnd2
                    doSomething sHnd3

  1. В данном случае это не так принципиально, но под управлением устройством разумеется подразумевается изменение его состояния.

  2. Если у вас монада головного мозга конечно. Достаточно моноида, же.