Этот пост - грамотный Хаскелл. Просто вставьте файл, например "pad.lhs", и ghci
сможет его запустить.
> {-# LANGUAGE GADTs, Rank2Types #-}
> import Control.Monad
> import Control.Monad.ST
> import Data.STRef
Хорошо, поэтому я смог понять, как представить монаду ST
в чистом коде. Сначала мы начинаем с нашего ссылочного типа. Его особая ценность не очень важна. Самое главное, что PT s a
не должно быть изоморфным любому другому типу forall s
. (В частности, он должен быть изоморфен ни ()
, ни Void
.)
> newtype PTRef s a = Ref {unref :: s a} -- This is defined liked this to make `toST'` work. It may be given a different definition.
Вид для s
равен *->*
, но это не очень важно сейчас. Это может быть polykind, для всего, что нам нужно.
> data PT s a where
> MkRef :: a -> PT s (PTRef s a)
> GetRef :: PTRef s a -> PT s a
> PutRef :: a -> PTRef s a -> PT s ()
> AndThen :: PT s a -> (a -> PT s b) -> PT s b
Довольно прямо. AndThen
позволяет использовать это как Monad
. Вам может быть интересно, как реализовать return
. Вот пример его монады (он только уважает законы монады относительно runPF
, которые будут определены позже):
> instance Monad (PT s) where
> (>>=) = AndThen
> return a = AndThen (MkRef a) GetRef --Sorry. I like minimalism.
> instance Functor (PT s) where
> fmap = liftM
> instance Applicative (PT s) where
> pure = return
> (<*>) = ap
Теперь мы можем определить fib
в качестве тестового примера.
> fib :: Int -> PT s Integer
> fib n = do
> rold <- MkRef 0
> rnew <- MkRef 1
> replicateM_ n $ do
> old <- GetRef rold
> new <- GetRef rnew
> PutRef new rold
> PutRef (old+new) rnew
> GetRef rold
И он проверяет тип. Ура! Теперь я смог преобразовать это в ST
(теперь мы видим, почему s
должен был быть * -> *
)
> toST :: PT (STRef s) a -> ST s a
> toST (MkRef a ) = fmap Ref $ newSTRef a
> toST (GetRef (Ref r)) = readSTRef r
> toST (PutRef a (Ref r)) = writeSTRef r a
> toST (pa `AndThen` apb) = (toST pa) >>= (toST . apb)
Теперь мы можем определить функцию для запуска PT
без ссылки на ST
:
> runPF :: (forall s. PT s a) -> a
> runPF p = runST $ toST p
runPF $ fib 7
дает 13
, что верно.
Мой вопрос: можем ли мы определить runPF
без ссылки с помощью ST
?
Есть ли чистый способ определить runPF
? PTRef
определение совершенно неважно; в любом случае это только тип заполнителя. Он может быть переопределен тем, что заставляет его работать.
Если вы не можете определить runPF
чисто, дайте доказательство, что оно не может.
Производительность не вызывает беспокойства (если бы это было так, я бы не сделал, чтобы каждый return
имел свой собственный ref).
Я думаю, что экзистенциальные типы могут быть полезны.
Примечание. Это тривиально, если мы предположим, что a
является динамическим или что-то еще. Я ищу ответ, который работает со всеми a
.
Примечание. Фактически, ответ даже не обязательно имеет отношение к PT
. Он просто должен быть таким же мощным, как ST
без использования магии. (Преобразование из (forall s. PT s)
является чем-то вроде проверки правильности ответа или нет.)