2015-03-27 13 views
6

Używam standardowej Monady IO.Jak zaimplementować zwarcie z Monadą IO w Scali

I w pewnym momencie potrzebuję skrótu. Przy danym warunku nie chcę uruchamiać następującego ios.

Oto moje rozwiązanie, ale okazało się to zbyt rozwlekły i nie elegancki:

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 … 

Na przykład dla 3, 4 i 5 linii, muszę powtórzyć ten sam warunek.

Czy istnieje lepszy sposób?

+2

Czy potrzebujesz wartości 'a',' b', 'c' itd., Czy też zależy Ci tylko na efektach? 'OptionT [IO,?]' Rodzaj dźwięków jak to, czego szukasz, ale to po prostu dałoby ci 'Brak' jeśli to zwarcie. –

+0

Potrzebuję tych wartości: jako warunek dla zwarcia, a także w klauzuli yield. –

+0

io ma efekt uboczny, więc metoda run może zwracać inną wartość po każdym wywołaniu. –

Odpowiedz

1

W rzeczywistości niczego tam nie zwieszałeś. W dalszym ciągu korzystasz z OI; po prostu nie przechwytujesz wartości.

Ponadto, standardowa monada IO nie definiuje filter (lub withFilter), więc nie można używać strażników w swoim rozumieniu.

Teraz, jeśli chcesz po prostu to, co pan powiedział (tę samą logikę, tylko bardziej suchy), zawsze można przypisać zmienną tymczasową w do zrozumienia:

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 … 

Ale jeśli rzeczywiście chcesz krótki -circuit, będziesz musiał rozdzielić przypadki w jakiś sposób. Oto jedna z możliwości, zakładając, że po prostu chce się pakować wszystko do tablicy tak typ zwracany jest proste, a IO towarzysz obiekt ma sposobu stosowania, które można wykorzystać, aby stworzyć coś, że po prostu zwraca wartość:

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) 
) 
) 

Jeśli twoje typy zwrotu są bardziej skomplikowane, być może będziesz musiał pracować ciężej z określaniem typów.

FWIW, warto zauważyć karę, którą płacisz za trzymanie rzeczy zapakowanych w monady; bez, ta sama logika byłaby (na przykład):

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

A jeśli pozwalają zwrotów dostaniesz:

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()) 

Jest to również możliwe w zasadzie do dekoracji Monada IO z dodatkowych metod, które pomagają się nieco, ale standardowy withFilter nie będzie działał, więc nie będziesz w stanie użyć cukru syntaktycznego dla zrozumienia.

+0

Czy możesz wyjaśnić, dlaczego 'withFilter' nie działa? –

+0

Wdyt o dodaniu tej metody do IO: def z Guardem [B] (f: A => IO [B], wartownik: A => Boolean, konstruktor: A => B): IO [B] = nowy IO [ B] { def uruchomić = { val a = self.run if (osłona (a)) f (a) .run innego konstruktora (a) } } –

+0

@YannMoisan - No, to naprawdę zależy od podpisania metod. Zwarcie na początku ma tendencję do zmiany sygnatury metody, której nie można wykonać przy pomocy filtra.Nie jestem pewien na pierwszy rzut oka, czy 'withGuard' będzie działał, ale nie jest trudno dowiedzieć się, czy sprawiłeś, że twoje IO będzie drukowane i wypróbowane! –