2014-07-07 17 views
13

Język Go ma instrukcję select, której można użyć do odpytywania wielu kanałów i wykonania określonej czynności w zależności od tego, który kanał jest niepusty.Jak zaimplementować ekwiwalent instrukcji Go's select dla kanałów Haskell STM?

E.g.

select { 
    case a := <- chanA: 
    foo(a) 
    case b := <- chanB: 
    baz(b) 
    case c := <- chanC: 
    bar(c) 
} 

To będzie czekać, aż albo chanA, chanB lub chanC jest niepusty, to jeśli na przykład chanB jest niepusty, to odczytać z chanB i zapisać wynik w b, następnie zadzwonić baz(b). Można także dodać klauzulę default:, co oznacza, że ​​instrukcja select nie będzie czekać na kanały i zamiast tego zrobi wszystko, co klauzula default, jeśli wszystkie kanały są puste.

Jaki byłby najlepszy sposób wdrożenia czegoś takiego dla STM TChan s w Haskell? Można to zrobić naiwnie za pomocą łańcucha if-else: sprawdzanie, czy każdy z nich ma wartość isEmptyChan, a jeśli nie jest pusty, to odczytanie go i wywołanie odpowiedniej funkcji lub wywołanie retry, jeśli wszystkie kanały są puste. Zastanawiałem się, czy nie byłoby bardziej eleganckiego/idiomatycznego sposobu na zrobienie tego?

Uwaga: instrukcja Go może zawierać również instrukcje wysyłania w swoich przypadkach i wypełnia tylko instrukcję wysyłania, jeśli jej kanał jest pusty. Byłoby wspaniale, gdyby ta funkcjonalność również mogła zostać powielona, ​​chociaż nie jestem pewien, czy byłby to elegancki sposób.

jedynie nieznacznie podobne ale coś Właśnie zauważyłem i nie jestem pewien, gdzie ją pisać: nie literówka na stronie w opisie Control.Monad.STM dla retry:

„Wdrożenie może zablokować wątek aż pewnego z telewizyjnych, które przeczytał, został udpated. "

+1

Możesz chcieć spojrzeć na 'r ace' z 'Control.Concurrent.Async'. –

+4

Warto zauważyć, że funkcja go nie wykonuje pierwszej dostępnej akcji, ale jest dostępna, wybrana losowo. To nie będzie głodować kanałów tylko dlatego, że są zdefiniowane później lub pecha na ścieżce wyboru. – Dustin

+0

Jest to całkowicie odmienne od "wybierz" Go. Kanały w Go są ograniczone, w przeciwieństwie do 'TChan' (dzięki czemu są rzeczywiście użyteczne), a' select' może być używane z operacjami wysyłania. – rightfold

Odpowiedz

5

Głód unikając

foreverK :: (a -> m a) -> a -> m() 
foreverK loop = go 
where go = loop >=> go 

-- Existential, not really required, but feels more like the Go version 
data ChanAct = Action (TChan a) (a -> STM()) 

perform :: STM() 
perform (Action c a) = readTChan c >>= a 

foreverSelectE :: [ChanAct] -> STM() 
foreverSelectE = foreverSelect . map perform 

foreverSelect :: [STM()] -> STM() 
foreverSelect = foreverK $ \xs -> first xs >> return (rotate1 xs) 

-- Should only be defined for non-empty sequences, but return() is an okay default. 
-- Will NOT block the thread, but might do nothing. 
first :: [STM()] -> STM() 
first = foldr orElse (return()) 

-- Should only be defined for non-empty sequences, really. 
-- Also, using a list with O(1) viewL and snoc could be better. 
rotate1 :: [a] -> [a] 
rotate1 [] = [] 
rotate1 (h:t) = t ++ [h] 

example = foreverSelectE 
    [ Action chanA foo 
    , Action charB baz 
    , Action chanC bar 
    ] 

Aby uniknąć na zawsze, można zamiast mieć mkSelect :: [STM()] -> STM (STM()) że "ukrywa" a TVAR [STM()] i obraca go każdy jest używany jak:

example1 :: STM() 
example1 = do 
    select <- mkSelect [actions] -- Just set-up 
    stuff1 
    select -- does one of the actions 
    stuff2 
    select -- does one of the actions 

main = OpenGL.idleCallback $= atomically example1 

Rozszerzając tę ​​technikę, możesz wybrać opcję, która zgłosiła, jeśli wykonała akcję lub wykonaną czynność, lub nawet zapętliła się, aż wszystkie działania zostaną zablokowane, itp.

11

można zaimplementować select semantykę (zarówno do odczytu i zapisu) używając orElse (uwaga: jest to specyficzne dla GHC). Na przykład:

forever $ atomically $ 
    writeTChan chan1 "hello" `orElse` writeTChan chan2 "world" `orElse` ... 

Chodzi o to, że gdy jeden prób działań (np piszesz do chan, ale jest pełna, albo czytasz z chan, ale jest pusta), wykonuje się drugą akcję. Instrukcja default to tylko return() jako ostatnia akcja w łańcuchu.

Dodaj: Jak zauważyłem @ Dustin, proszę wybrać losowy oddział z ważnego powodu. Prawdopodobnie najłatwiejszym rozwiązaniem jest przetasowanie akcji w każdej iteracji, w większości przypadków powinno być ok. Powtarzanie prawidłowej semantyki (tasowanie tylko aktywnych gałęzi) jest nieco trudniejsze. Prawdopodobnie ręczna kontrola isEmptyChan dla wszystkich gałęzi jest drogą do zrobienia.

+3

Czy to nie będzie miało problemu z głodem, jak powiedział Dustin powyżej? – Dan

+3

@ Dan Mam nadzieję, że masz rację: (zakładając, że przykład Yurasa używał 'readTChan')' readTChan chanN' będzie zawsze czytany od kiedy wszystkie kanały jberryman

+0

@ Dan Masz rację, dodałem notatkę. – Yuras

Powiązane problemy