2013-06-12 39 views
11

Tôi có đoạn mã sau sử dụng đơn Reader để cấu hình và cũng phải xử lý IO[Option[String]] và tôi đã kết thúc bằng mã mà bước cầu thang trong hàm encode của tôi.Làm thế nào để tránh cầu thang bước với Monad Transformers trong scala?

Làm cách nào tôi có thể xây dựng một biến áp đơn lẻ cho ReaderOptionT để tránh hàm lồng nhau xấu xí for trong hàm encode của tôi?

def encode(fileName: String): Reader[Config, IO[Unit]] = for { 
    ffmpegWrapper <- findFfmpegWrapper 
    ffmpegBin <- findFfmpeg 
} yield (for { 
    w <- ffmpegWrapper 
    b <- ffmpegBin 
    stream <- callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT] 
} yield stream) map (_ foreach (println)) getOrElse Unit.box {} 


def getCommand(ffmpegWrapper: String, ffmpegBin: String, 
      videoFile: String) = s"$ffmpegWrapper $ffmpegBin $videoFile '-vcodec libx264 -s 1024x576' /tmp/out.mp4" 

def callFfmpeg(command: String): IO[Stream[String]] = IO { 
    Process(command).lines_! 
} 

def findFile(path:List[String]): OptionT[IO,String] = OptionT[IO,String](IO{path.find(new File(_).exists)}) 

def findFfmpeg:Reader[Config, OptionT[IO,String]] = Reader {c=>findFile(c.ffmpegLocations)} 

def findFfmpegWrapper:Reader[Config, OptionT[IO,String]] = Reader {c=>findFile(c.ffmpegWrapperLocations)} 

Cảm ơn!

Trả lời

13

Nếu bạn nhìn vào definition of Reader in the Scalaz source, bạn sẽ thấy điều này:

type Reader[-E, +A] = ReaderT[Id, E, A] 

nào cho chúng ta biết rằng Reader đơn nguyên bạn đang sử dụng chỉ là một chuyên môn hóa của một máy biến áp đơn nguyên nơi đơn nguyên được bọc là tầm thường Id đơn nguyên. Bạn có thể sử dụng trực tiếp ReaderT nhưng gói OptionT[IO, _] đơn vị thay vì chỉ gói mọi thứ trong một Reader. Ví dụ, sau đây nên làm những gì bạn muốn:

type OptionIO[+A] = OptionT[IO, A] 

def findFfmpeg: ReaderT[OptionIO, Config, String] = 
    Kleisli[OptionIO, Config, String](c => findFile(c.ffmpegLocations)) 

def findFfmpegWrapper: ReaderT[OptionIO, Config, String] = 
    Kleisli[OptionIO, Config, String](c => findFile(c.ffmpegWrapperLocations)) 

def encode(fileName: String): ReaderT[OptionIO, Config, Unit] = (for { 
    w <- findFfmpegWrapper 
    b <- findFfmpeg 
    stream <- Kleisli[OptionIO, Config, Stream[String]](
    _ => callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT] 
    ) 
} yield stream).map(_ foreach println) 

Về nguyên tắc bạn sẽ có thể thay thế các phần sau stream <- như sau:

callFfmpeg(getCommand(w, b, fileName)).liftM[OptionT].liftReaderT[Config] 

Nhưng vì lý do nào Unapply máy móc mà liftReaderT dựa vào dường như không hoạt động trong trường hợp này. Viết Kleisli một phần rõ ràng không phải là tất cả những gì khủng khiếp, may mắn thay.


Như một chú thích: các đẹp liftReaderT cú pháp tôi đã đề cập trở nên có sẵn nếu bạn xác định một thể hiện UnapplyCo như thế này:

implicit def unapplyMFA1[TC[_[_]], F[+_], M0[F[+_], +_], A0](
    implicit TC0: TC[({ type L[x] = M0[F, x] })#L] 
): UnapplyCo[TC, M0[F, A0]] { 
    type M[+X] = M0[F, X] 
    type A = A0 
} = new UnapplyCo[TC, M0[F, A0]] { 
    type M[+X] = M0[F, X] 
    type A = A0 
    def TC = TC0 
    def leibniz = Leibniz.refl 
} 

Tôi không chắc chắn ra khỏi đỉnh đầu của tôi cho dù có một lý do Scalaz 7 hiện không cung cấp trường hợp này, nhưng nó có thể đáng xem xét.

+1

Tôi đang trong quá trình viết lên một câu trả lời giống hệt như khi bạn xuất hiện. Tôi đã sửa đổi phần đầu câu trả lời của bạn với nội dung nào đó của tôi nhưng không phải trong của bạn về hiển thị bí danh loại Reader = ReaderT, vui lòng xóa nó nếu bạn cho rằng nó không thêm vào câu trả lời của bạn :) – stew

+0

@stew: Cảm ơn ! Tôi vừa thêm liên kết vào nguồn bạn đã đề cập. –

+0

Travis và @stew điều này cực kỳ hữu ích! Chỉ cần thử nó ngay bây giờ. – cwmyers