2016-12-13 10 views
1

는 데이터 유형 F 및 유형의 기능을 제공하는 라이브러리가`IO` 모나드에서 실행되는 계산을 종료하는 법?

ffoldlIO :: (b -> a -> IO b) -> b -> F a -> IO b 

기능은

foldlIO :: (b -> a -> IO b) -> b -> [a] -> IO b 
foldlIO f a = \xs -> foldr (\x r (!a') -> f a' x >>= r) return xs a 

내가 foldlIO (따라서 ffoldlIO)이 짧은에서 실행할 수 있는지 여부를 궁금해 유사하다 - 회로 패션. 우리는 예외 계산을 중지 한 다음 그것을 잡으려고을 던져

여기
example1 :: IO Int 
example1 = foldlIO (\a x -> if a < 4 then return (a + x) else return a) 0 [1..5] 

foldlIO 전체 목록,하지만를 통과 :

이 예제를 고려? 다음과 같이 입력하십시오 :

data Terminate = Terminate 
    deriving (Show) 

instance Exception Terminate 

example2 :: IO Int 
example2 = do 
    ra <- newIORef 0 
    let step a x 
     | a' < 4 = return a' 
     | otherwise = writeIORef ra a' >> throwIO Terminate 
     where a' = a + x 
    foldlIO step 0 [1..] `catch` \(_ :: Terminate) -> readIORef ra 

믿을만한가요? IO 모나드 (및 다른 모나드)에서 실행되는 계산을 종료하는 더 좋은 방법이 있습니까? 아니면이 작업을 전혀 수행하지 않아도됩니까?

답변

3

예를 들어, 다음과 같이 ContT 모나드 변압기를 사용할 수 있습니다

example3 :: IO Int 
example3 = flip runContT return . callCC $ \exit -> do 
    let step a x 
      | a' < 4 = return a' 
      | otherwise = exit a' 
      where a' = a + x 
    foldM step 0 [1..] 

또한, 당신이 종료있는 posibility 당신에게 foldM의 자신의 버전을 정의 할 수 있습니다.

termFoldM :: (Monad m, Foldable t) => 
    ((b -> ContT b m c) -> b -> a -> ContT b m b) -> b -> t a -> m b 
termFoldM f a t = flip runContT return . callCC $ \exit -> foldM (f exit) a xs 

example4 :: IO Int 
example4 = termFoldM step 0 [1..] 
    where 
    step exit a x 
     | a' < 4 = return a' 
     | otherwise = exit a' 
     where a' = a + x 

그러나이 방법은 (ContT) 하나의 문제점이 있습니다. 일부 IO 작업을 쉽게 수행 할 수 없습니다. 예를 들어 step 함수는 ContT Int IO Int이 아닌 IO Int 값을 반환해야하므로이 코드는 컴파일되지 않습니다.

let step a x 
     | a' < 4 = putStrLn ("'a = " ++ show a') >> return a' 
     | otherwise = exit a' 
     where a' = a + x 

다행히,이처럼 lift 기능에 의해이 문제를 해결 할 수 있습니다

let step a x 
     | a' < 4 = lift (putStrLn ("'a = " ++ show a')) >> return a' 
     | otherwise = exit a' 
     where a' = a + x 
+1

이것은 다소 비슷합니다. 'ContT'는 초심자로 유명하기 때문에 더 많은 코멘트가 도움이 될 것입니다. 나는 또한'lift (putStrLn "foo") >> ...'를 어딘가에 추가하여'ContT _ IO _' 내에서 IO를하는 방법을 보여줄 것을 제안 할 것이다. – chi

+0

이 계산은 내가 요청한대로'IO' 대신'ContT Int IO Int'에서 실행됩니다. 나는 그 질문을 덜 혼란스럽게 만들기 위해 편집 할 것이다, 미안하다. – user3237465

+0

"당신은 자신 만의'foldM' 버전을 정의 할 수 있습니다. - 할 수 없습니다 : 라이브러리는'ffoldlIO' 만 제공하고 제 자신의'foldM'을 구현하기에는 너무 복잡합니다. 질문의 목록은 내가 성취하고자하는 것을 보여주는 예일뿐입니다. – user3237465

1

내 첫 번째 대답이 정확하지 않았다. 그래서, 나는 향상 시키려고 노력할 것이다.


IO 모나드에서 종료하기 위해 예외를 사용하는 것은 해킹이 아니라 깨끗하게 보입니다. 나는이 같은 인스턴스 MonadCont IO을 정의하기 위해 제안 :

data Terminate = forall a . Terminate a deriving (Typeable) 
instance Show Terminate where show = const "Terminate" 
instance Exception Terminate 

instance MonadCont IO where 
    callCC f = f exit `catch` (\(Terminate x) -> return . unsafeCoerce $ x) 
     where exit = throwIO . Terminate 

그런 다음 당신이 당신의 예를 더 청소기 다시 작성할 수 있습니다. IOREf

example :: IO Int 
example = callCC $ \exit -> do 
    let step a x 
      | a' < 4 = return a' 
      | otherwise = exit a' 
      where a' = a + x 
    foldlIO step 0 [1..] 

변형.

data Terminate = Terminate deriving (Show, Typeable) 
instance Exception Terminate 

instance MonadCont IO where 
    callCC f = do 
     ref <- newIORef undefined 
     let exit a = writeIORef ref a >> throwIO Terminate 
     f exit `catch` (\Terminate -> readIORef ref) 
+0

그것은 좋은 트릭, 감사합니다. 하지만 고아 인스턴스를 도입하고 싶지 않고 단락 폴드를 작성할 수있는 [기계류] (https://github.com/effectfully/prefolds)가 이미 있으므로 이러한 폴드를 해석해야합니다. 'IO' 모나드이고'Terminate' 접근법에 대해서는 아직 확실하지 않습니다. 이 접근법이 괜찮은지 확인하는 참고 자료가 있습니까? 그것을 언급하는 종이 또는 일반적으로 사용되는 라이브러리 또는 이와 유사한 것. – user3237465

+1

@ user3237465 아니요, 아닙니다. 그러나이 접근법에 대한 한 가지 잠재적 인 문제점을 알았습니다. 누구든지 모든 예외를 포착 할 수 있으며 이로 인해 프로그램이 손상 될 수 있습니다. 다행히도, 이것은있을 법하지 않습니다. 따라서 모든 곳에서이 방법을 사용하는 것은 좋지 않지만 어떤 경우에는 가능할 수 있습니다. – freestyle

+0

좋은 관찰입니다. 그렇다면 나는 누적기를 'IORef'에 저장하고, 최종 값은 예외가 던져 졌는지 여부에 관계없이 항상 'IORef'에있을 것입니다. – user3237465