Подключение Producer и Pipe для извлечения результата

Имея производителя типа Producer ByteString IO () и канал типа Pipe ByteString a IO (), как мне составить эффект, который при запуске приведет к IO a?

Вот моя лучшая попытка:

{-# LANGUAGE ScopedTypeVariables #-}
import Pipes
import Data.ByteString

run :: forall a. IO a
run = runEffect $ 
  (undefined :: Producer ByteString IO ()) >-> (undefined :: Pipe ByteString a IO ())

Это не удается со следующим:

Couldn't match type `Void' with `()'
Expected type: IO a
  Actual type: IO ()
In the expression:
  runEffect
  $ (undefined :: Producer ByteString IO ())
    >-> (undefined :: Pipe ByteString a IO ())
In an equation for `run':
    run
      = runEffect
        $ (undefined :: Producer ByteString IO ())
          >-> (undefined :: Pipe ByteString a IO ())

person Nikita Volkov    schedule 16.10.2013    source источник
comment
Это полный источник и сообщение об ошибке? Выглядит очень странно! В конце концов, он говорит, что пытается объединить Void и (), но Void не появляется в ожидаемом типе.   -  person Daniel Wagner    schedule 17.10.2013
comment
@DanielWagner Void появляется в подписи Effect введите псевдоним. По поводу кода и ошибки - смотрите обновления.   -  person Nikita Volkov    schedule 17.10.2013
comment
Независимо от решения вашей проблемы, я бы пожаловался в штаб-квартиру GHC на то, насколько серьезна эта ошибка!   -  person Daniel Wagner    schedule 17.10.2013
comment
Вы хотите получить первое значение?   -  person Gabriel Gonzalez    schedule 17.10.2013
comment
Я подал запрос функции в GHC Trac, чтобы улучшить это сообщение об ошибке.   -  person Daniel Wagner    schedule 17.10.2013


Ответы (1)


Как правило, вам нужно составить Producer с Consumer, чтобы получить Effect, который может быть запущен runEffect. Это не то, что у вас здесь есть, но, к счастью, есть больше способов устранить Proxy, чем просто runEffect.

Подводя итог тому, что у нас есть, эта композиция заканчивается Producer.

pipe :: Producer a IO ()
pipe = (undefined :: Producer ByteString IO ()) >-> (undefined :: Pipe ByteString a IO ()) 

Модуль Pipes.Prelude содержит множество других способов устранения Producers, таких как Pipes.Prelude.last.

last :: Monad m => Producer a m () -> m (Maybe a)

Вероятно, самый общий способ получить as — это использовать Pipes.Prelude.fold.

fold :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Producer a m () -> m b

что похоже на runEffect, за исключением того, что оно уменьшает Producers до их основного Monad. Так как это то, что у нас есть, это будет отлично работать. Вот как мы можем реализовать Pipes.Prelude.head

slowHead = fold (\res a -> res <> First (Just a)) mempty getFirst

Хотя стоит отметить, что slowHead потребляет весь Producer (и, таким образом, выполняет все необходимые эффекты), а Pipes.Prelude.head выполняет только первый. Это намного ленивее!

person J. Abrahamson    schedule 16.10.2013
comment
Вы также можете использовать head для получения первого значения. Это будет лениво и будет запускать производителя достаточно долго, чтобы получить первый элемент. - person Gabriel Gonzalez; 17.10.2013
comment
Большой! Спасибо, парни! Итак, окончательный результат run = Pipes.head $ (undefined :: Producer ByteString IO ()) >-> (undefined :: Pipe ByteString a IO ()) с сигнатурой типа run :: forall a. IO (Maybe a) - person Nikita Volkov; 17.10.2013
comment
@GabrielGonzalez Будет ли решение с использованием head иметь тот же эффект, что и run = runEffect $ ((undefined :: Producer ByteString IO ()) >> return Nothing) >-> ((undefined :: Pipe ByteString a IO ()) >> return Nothing) >-> (await >>= return . Just)? - person Nikita Volkov; 17.10.2013
comment
Омф, да. Я изменю этот ответ, чтобы уточнить, что решение fold, которое я написал, потенциально имеет совершенно разные характеристики производительности... - person J. Abrahamson; 17.10.2013
comment
@NikitaVolkov Да. head просто более эффективен. - person Gabriel Gonzalez; 17.10.2013