Используйте MonadRef для реализации MonadCont

Существует хорошо известная проблема, заключающаяся в том, что мы не можем использовать типы forall в возврате Cont тип.

Однако должно быть нормально иметь следующее определение:

class Monad m => MonadCont' m where
    callCC' :: ((a -> forall b. m b) -> m a) -> m a
    shift :: (forall r.(a -> m r) -> m r) -> m a
    reset :: m a -> m a

а затем найти экземпляр, который имеет смысл. В этой статье автор заявил, что мы можем реализовать MonadFix поверх ContT r m предоставления что m реализовал MonadFix и MonadRef. Но я думаю, что если у нас есть MonadRef, мы можем реализовать callCC' выше, как показано ниже:

--satisfy law: mzero >>= f === mzero
class Monad m => MonadZero m where
    mzero :: m a

instance (MonadZero m, MonadRef r m) => MonadCont' m where
    callCC' k = do
        ref <- newRef Nothing
        v <- k (\a -> writeRef ref (Just a) >> mzero)
        r <- readRef ref
        return $ maybe v id r
    shift = ...
    reset = ...

(К сожалению, я не знаком с семантикой shift и reset, поэтому я не предоставлял им реализацию)

Эта реализация кажется мне нормальной. Интуитивно понятно, что когда вызывается callCC', мы передаем k, который функция, которая сама по себе всегда терпит неудачу (хотя мы не можем предоставить значение произвольного типа b, но мы всегда можем предоставить mzero типа m b и в соответствии с законом это должен эффективно остановить вычисление всех дальнейших эффектов), и он фиксирует полученное значение как окончательный результат callCC'.

Итак, мой вопрос:

Работает ли эта реализация так, как ожидалось, для идеального callCC? Можем ли мы реализовать shift и reset с правильной семантикой?

В дополнение к вышесказанному, я хочу знать:

Чтобы обеспечить правильное поведение, мы должны принять некоторое свойство MonadRef. Итак, какие законы должны быть MonadRef, чтобы приведенная выше реализация работала так, как ожидалось?

ОБНОВЛЕНИЕ

Оказывается, приведенная выше наивная реализация недостаточно хороша. Чтобы он удовлетворял «Продолжение тока»

callCC $\k -> k m === callCC $ const m === m

Мы должны настроить реализацию на

instance (MonadPlus m, MonadRef r m) => MonadCont' m where
    callCC' k = do 
       ref <- newRef mzero
       mplus (k $ \a -> writeRef ref (return a) >> mzero) (join (readRef ref))

Другими словами, исходного MonadZero недостаточно, мы должны иметь возможность комбинировать значение mzero с обычным вычислением без отмены всего вычисления.

Вышеизложенное не дает ответа на вопрос, оно просто скорректировано, так как первоначальная попытка стать кандидатом была сфальсифицирована. Но для обновленной версии первоначальные вопросы остаются вопросами. В частности, reset и shift еще предстоит реализовать.


person Earth Engine    schedule 16.06.2014    source источник
comment
является ли ваше обновление ответом на ваш вопрос? если да, может быть, вы могли бы опубликовать его как таковой, чтобы вопрос больше не считался без ответа?   -  person sclv    schedule 20.02.2015


Ответы (1)


(Это еще не ответ, но мне пришли в голову только некоторые подсказки. Я надеюсь, что это приведет к настоящему ответу, самому или кем-то еще.)

Вызов по значению дублирует вызов по имени -- Филип Уодлер

В приведенной выше статье автор представил «двойственное исчисление», типизированное исчисление, соответствующее классической логике. В последнем разделе есть сегмент, в котором говорится

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

Как указано в статье Вадлера, вызов по имени с готовностью оценивает продолжения (он возвращается до того, как все значения будут оценены), в то время как вызов по значению оценивает продолжения лениво (он возвращается только после оценки всех значений).

Теперь взгляните на callCC' выше, я считаю, что это пример двойного вызова по необходимости на стороне продолжения. Стратегия оценки заключается в том, чтобы предоставить ложное «продолжение» заданной функции, но кэшировать состояние в этот момент, чтобы позже вызвать «истинное» продолжение. Это чем-то похоже на кэширование продолжения, и поэтому после завершения вычислений мы восстанавливаем это продолжение. Но кеширование оцененного значения — это то, что подразумевается под вызовом по необходимости.

Вообще я подозреваю, что состояние (вычисление до текущего момента времени) двойственно продолжению (будущее вычисление). Это объясняет некоторые явления. Если это так, то неудивительно, что MonadRef (соответствуют глобальному и полиморфному состоянию) двойственны MoncadCont (соответствуют глобальному и полиморфному продолжениям), и поэтому их можно использовать для реализации друг друга.

person Community    schedule 03.07.2015