2015-03-27 15 views
6

Tôi sử dụng một đơn IO chuẩn.Làm thế nào để thực hiện một đoạn ngắn mạch với IO monad trong Scala

Và tại một thời điểm nào đó, tôi cần phải rút ngắn mạch. Trên một điều kiện nhất định, tôi không muốn chạy các ios sau đây.

Đây là giải pháp của tôi, nhưng tôi thấy nó quá dài dòng và không thanh lịch:

def shortCircuit[A](io: IO[A], continue: Boolean) = 
    io.map(a => if (continue) Some(a) else None) 

    for { 
    a <- io 
    b <- shortCircuit(io, a == 1) 
    c <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    d <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    e <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    } yield … 

Ví dụ, đối thứ 3, thứ 4 và thứ 5 dòng, tôi cần phải lặp lại tình trạng tương tự.

Có cách nào tốt hơn không?

+2

Bạn có cần các giá trị của 'a',' b', 'c', v.v. hay bạn chỉ quan tâm đến các hiệu ứng? 'OptionT [IO,?]' Loại âm thanh giống như những gì bạn đang tìm kiếm, nhưng nó sẽ chỉ cung cấp cho bạn một 'None' nếu nó ngắn mạch. –

+0

Tôi cần các giá trị này: làm điều kiện cho đoản mạch và cũng trong điều khoản lợi nhuận. –

+0

io có tác dụng phụ, do đó phương thức chạy có thể trả về một giá trị khác sau mỗi cuộc gọi. –

Trả lời

1

Bạn chưa thực sự thu gọn bất kỳ thứ gì ở đó. Bạn vẫn đang chạy các IO; bạn chỉ không nắm bắt được các giá trị.

Ngoài ra, đơn nguyên IO chuẩn không xác định filter (hoặc withFilter), vì vậy bạn không thể sử dụng các nhân viên bảo vệ để hiểu.

Bây giờ, nếu bạn muốn chỉ là những gì bạn đã nói (giống logic, chỉ DRY hơn), bạn luôn có thể gán một biến tạm thời trong cho sự hiểu biết:

for { 
    a <- io 
    b <- shortCircuit(io, a == 1) 
    continue = b.map(_ == 1).getOrElse(false) 
    c <- shortCircuit(io, continue) 
    d <- shortCircuit(io, continue) 
    e <- shortCircuit(io, continue) 
} yield … 

Nhưng nếu bạn thực sự muốn ngắn -circuit, bạn sẽ phải phá vỡ các trường hợp trong một số cách. Dưới đây là một khả năng, giả sử bạn chỉ muốn để đóng gói tất cả mọi thứ vào một mảng nên kiểu trả về là đơn giản, và IO đối tượng bạn đồng hành của bạn có áp dụng phương pháp mà bạn có thể sử dụng để tạo ra một cái gì đó mà chỉ trả về một giá trị:

io.flatMap(a => 
    if (a == 1) IO(() => Array(a)) 
    else io.flatMap(b => 
    if (b == 1) IO(() => Array(a,b)) 
    else for { 
     c <- io 
     d <- io 
     e <- io 
    } yield Array(a,b,c,d,e) 
) 
) 

Nếu các kiểu trả về của bạn phức tạp hơn, bạn có thể phải làm việc chăm chỉ hơn với việc chỉ định các kiểu.

FWIW, điều đáng chú ý là hình phạt mà bạn phải trả để giữ mọi thứ được bọc trong monads; mà không có, cùng một logic sẽ được (ví dụ):

io() match { 
    case 1 => Array(1) 
    case a => io() match { 
    case 1 => Array(a, 1) 
    case b => Array(a, b, io(), io(), io()) 
    } 
} 

Và nếu bạn cho phép trở về, bạn nhận được:

val a = io() 
if (a == 1) return Array(a) 
val b = io() 
if (b == 1) return Array(a, b) 
Array(a, b, io(), io(), io()) 

Cũng có thể về nguyên tắc để trang trí các đơn nguyên với các phương pháp bổ sung mà giúp IO một chút, nhưng tiêu chuẩn withFilter sẽ không hoạt động, do đó bạn sẽ không thể sử dụng đường cú pháp để hiểu.

+0

Bạn có thể giải thích tại sao 'withFilter' không hoạt động? –

+0

Wdyt về cách thêm phương thức này trên IO: def withGuard [B] (f: A => IO [B], bảo vệ: A => Boolean, trình tạo: A => B): IO [B] = new IO [ B] { def run = { val a = self.run if (guard (a)) f (a) .run builder khác (a) } } –

+0

@YannMoisan - Vâng, nó thực sự phụ thuộc vào chữ ký của các phương pháp. Ngắn mạch sớm có xu hướng thay đổi chữ ký phương thức, mà bạn không thể làm với bộ lọc.Tôi không chắc chắn trong nháy mắt cho dù 'withGuard' sẽ làm việc, nhưng nó không phải là rất khó để tìm hiểu nếu bạn làm cho IO của bạn được printlns và thử nó ra! –

Các vấn đề liên quan