2009-07-15 11 views
12

Mam niektóre funkcje napisane w C, które nazywam z Haskell. Funkcje te zwracają IO (CInt). Czasami chcę uruchomić wszystkie funkcje, niezależnie od tego, co powróci, i to jest łatwe. Przez wzgląd na przykład kodu, jest to ogólna idea tego, co dzieje się obecnie:Haskell: monadyczne pobieranie?

Prelude> let f x = print x >> return x 
Prelude> mapM_ f [0..5] 
0 
1 
2 
3 
4 
5 
Prelude> 

dostaję moje pożądanych efektów ubocznych, a ja nie dbam o wynikach. Ale teraz muszę zatrzymać wykonywanie natychmiast po pierwszym przedmiocie, który nie zwraca pożądanego wyniku. Powiedzmy, że wartość zwracana 4 lub wyższa wymaga wykonanie zatrzymać - to co Chcę zrobić to w ten sposób:

Prelude> takeWhile (<4) $ mapM f [0..5] 

co daje mi ten błąd:

 
<interactive>:1:22: 
    Couldn't match expected type `[b]' against inferred type `IO a' 
    In the first argument of `mapM', namely `f' 
    In the second argument of `($)', namely `mapM f ([0 .. 5])' 
    In the expression: takeWhile (< 4) $ mapM f ([0 .. 5])

I to ma sens ja - wynik wciąż jest zawarty w monadzie IO i nie mogę po prostu porównać dwóch wartości zawartych w monadzie IO. Wiem, że to jest dokładnie cel monad - łączenie wyników razem i odrzucanie operacji, gdy spełniony jest pewien warunek - ale jest tam łatwy sposób na "zawinięcie" monografii IO w tym przypadku, aby zatrzymać wykonywanie łańcucha pod warunkiem z mojego wyboru, bez pisania instancji MonadPlus?

Czy mogę po prostu "cofnąć" wartości z f, dla celów czasu trwania?

Czy to jest rozwiązanie, w którym mieszczą się funktory? Funktorzy jeszcze mnie nie "kliknęli", ale mam wrażenie, że to dobra sytuacja, żeby z nich skorzystać.


Aktualizacja:

@sth ma najbliższy odpowiedź na to, co chcę - w rzeczywistości, to niemal dokładnie to, co się dzieje za, ale to wciąż chciał zobaczyć, czy jest tam standardowe rozwiązanie, które nie jest jawnie rekurencyjne - to przecież Haskell! Patrząc wstecz, jak sformułowałem moje pytanie, teraz widzę, że nie byłem wystarczająco jasny o moim pożądanym zachowaniu.

Funkcja f, której użyłem powyżej dla przykładu, była jedynie przykładem. Prawdziwe funkcje są napisane w języku C i używane wyłącznie dla ich skutków ubocznych. Nie mogę użyć sugestii @ Toma: mapM_ f (takeWhile (<4) [0..5]), ponieważ nie mam pojęcia, czy jakiekolwiek dane wejściowe rzeczywiście doprowadzą do sukcesu lub niepowodzenia, dopóki nie zostaną wykonane.

Nie zwracam uwagi na zwróconą listę - chcę tylko wywoływać funkcje C, dopóki lista nie zostanie wyczerpana lub pierwsza funkcja C zwróci kod błędu.

W stylu C Pseudokod, moje zachowanie byłoby:

do { 
    result = function_with_side_effects(input_list[index++]); 
} while (result == success && index < max_index); 

więc ponownie, odpowiedź @ STH za wykonuje dokładne zachowanie, które chcę, oprócz tego, że wyniki mogą (powinny?) Zostać odrzucone. Funkcja dropWhileM_ byłaby odpowiednia dla moich celów. Dlaczego nie ma takiej funkcji lub takeWhileM_ w Control.Monad? Widzę, że było a similar discussion on a mailing list, ale wydaje się, że nic z tego nie wynika.

+0

To także trochę rozczarowuje, że 'sortBy' nie jest zdefiniowany w terminach' sortByM :: Monad m => (a -> a -> m Ordering) -> [a] -> m [a] '. –

Odpowiedz

15

Można zdefiniować sequence jako

sequence xs = foldr (liftM2 (:)) (return []) xs 

Problem z liftM2 że już widząc to nie masz okazję zatrzymać m2, co może być launchTheMissiles!

liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c 
liftM2 f m1 m2 = do 
    x1 <- m1 
    x2 <- m2 
    return (f x1 x2) 

Korzystanie guard jak poniżej wydaje się atrakcyjne:

sequenceUntil p xs = foldr (myLiftM2 p (:)) (return []) xs 
    where myLiftM2 p f m1 m2 = do 
      x1 <- m1 
      guard $ p x1 
      x2 <- m2 
      return (f x1 x2) 

Powyższy kod nie powiedzie się w aplikacji, ponieważ monada IO nie jest instancją MonadPlus.

Tak trzymać swoją rękę trochę więcej

module Main where 

import Control.Monad 

printx :: Int -> IO Int 
printx x = do 
    print x 
    return x 

sequenceUntil :: (Monad m) => (a -> Bool) -> [m a] -> m [a] 
sequenceUntil p xs = foldr (myLiftM2 (:) []) (return []) xs 
    where myLiftM2 f z m1 m2 = do 
      x1 <- m1 
      if p x1 then do x2 <- m2 
          return $ f x1 x2 
        else return z 

main :: IO() 
main = do 
    let as :: [IO Int] 
     as = map printx [1..10] 
    ys <- sequenceUntil (< 4) as 
    print ys 

Nawet as jest lista działań ponad 1 do 10, wyjście jest

1 
2 
3 
4 
[1,2,3] 

Odrzucanie wyników jest następnie trywialny:

sequenceUntil_ :: (Monad m) => (a -> Bool) -> [m a] -> m() 
sequenceUntil_ p xs = sequenceUntil p xs >> return() 

main :: IO() 
main = do 
    let as :: [IO Int] 
     as = map printx [1..] 
    sequenceUntil_ (< 4) as 

Należy zanotować użycie [1..], która pokazuje nowy komunikat binator maintains laziness.


Może wolisz spanM:

spanM :: (Monad m) => (a -> Bool) -> [m a] -> m ([a], [m a]) 
spanM _ [] = return ([], []) 
spanM p (a:as) = do 
    x <- a 
    if p x then do (xs,bs) <- spanM p as 
       return (x:xs, bs) 
     else return ([x], as) 

Zauważ, że różni się nieco od span tym, że zawiera element uszkodzoną na liście wynikowej. Druga para to pozostałe akcje. Na przykład:

*Main> (xs,bs) <- spanM (< 4) as 
1 
2 
3 
4 
*Main> xs 
[1,2,3,4] 
*Main> sequence bs 
5 
6 
7 
8 
9 
10 
[5,6,7,8,9,10] 

Jeszcze inna alternatywa:

untilM :: Monad m => (a -> Bool) -> [m a] -> m() 
untilM p (x:xs) = do 
    y <- x 
    unless (p y) $ untilM p xs 

Należy zauważyć, że poczucie orzecznika uzupełniają:

*Main> untilM (>= 4) as 
1 
2 
3 
4 
+0

+1, dobra definicja 'sequenceWhile'. Najpierw próbowałem zdefiniować go za pomocą 'foldM' zamiast' foldr', ale to oczywiście nie zadziała, ponieważ to wciąż wymusza całą listę. –

+0

Ktoś zasugerował lepsze imię: sequenceUntil. Dodałem również opcję spanM jako alternatywę. –

10

Edytuj: Teraz widzę, czego szukasz.

gbacon opublikował fajną funkcję sequenceWhile, która jest prawie "prymitywną", jakiej potrzebujesz.

W rzeczywistości, ponieważ interesują Cię tylko efekty uboczne, powinno wystarczyć sequenceWhile_. Oto definicja (ponownie, zainspirowany gbacon, głosować go!):

sequenceWhile_ :: (Monad m) => (a -> Bool) -> [m a] -> m() 
sequenceWhile_ p xs = foldr (\mx my -> mx >>= \x -> when (p x) my) 
          (return()) xs 

Nazywasz to tak:

Prelude Control.Monad> sequenceWhile (<4) $ map f [1..] 

odpowiedź oryginalny:

Można po prostu "wyprostuj" wartości z Monady IO do użytku z takeWile, ale można "podnieść" takeWhile do użycia w Monad!

Funkcja (a -> b) przejmie funkcję (m a -> m b), gdzie m jest Monadą.

(Na marginesie, można znaleźć funkcję takiego szukając tego typu na Hoogle, w tym przypadku, wyszukując: Monad m => (a -> b) -> (m a -> m b))

Z liftM można to zrobić:

Prelude> :m + Control.Monad 
Prelude Control.Monad> let f x = print x >> return x 
Prelude Control.Monad> liftM (takeWhile (<4)) $ mapM f [0..5] 
0 
1 
2 
3 
4 
5 
[0,1,2,3] 

To może nie być to, czego chcesz. mapM zastosuje funkcję f do całej listy w kolejności, przed zwróceniem listy. Ta wynikowa lista jest następnie przekazywana do zniesionej funkcji takeWhile.

Jeśli chcesz zatrzymać drukowanie po trzecim elemencie, musisz przestać wywoływać drukowanie. Oznacza to, że nie należy stosować f do takiego elementu. Tak, będziesz skończyć z czegoś prostego jak:

Prelude> mapM_ f (takeWhile (<4) [0..5]) 

Nawiasem mówiąc, należy się zastanawiać, dlaczegomapMnajpierw wydrukować wszystko, przed powrotem do listy. Widać to poprzez zastąpienie funkcji z ich definicjami:

mapM f [0..1] 
= 
sequence (map f [0..1]) 
= 
sequence (f 0 : map f [1..1]) 
= 
sequence (f 0 : f 1 : []) 
= 
sequence ((print 0 >> return 0) : f 1 : []) 
= 
sequence ((print 0 >> return 0) : (print 1 >> return 1) : []) 
= 
do x <- (print 0 >> return 0) 
    xs <- (sequence ((print 1 >> return 1) : [])) 
    return (x:xs) 
= 
do x <- (print 0 >> return 0) 
    xs <- (do y <- (print 1 >> return 1) 
      ys <- sequence ([]) 
      return (y:ys)) 
    return (x:xs) 
= 
do x <- (print 0 >> return 0) 
    xs <- (do y <- (print 1 >> return 1) 
      ys <- return [] 
      return (y:ys)) 
    return (x:xs) 
= 
do x <- (print 0 >> return 0) 
    xs <- (do y <- (print 1 >> return 1) 
      return (y:[])) 
    return (x:xs) 
= 
do x <- (print 0 >> return 0) 
    xs <- (print 1 >> return (1:[])) 
    return (x:xs) 
= 
do x <- (print 0 >> return 0) 
    print 1 
    return (x:1:[]) 
= 
do print 0 
    print 1 
    return (0:1:[]) 

Ten proces zastępowania funkcji z ich definicjami nazywa equational rozumowanie.

Gdybym nie popełnić żadnych błędów, można teraz (mam nadzieję) widać, że mapM (używając sequence) najpierw drukuje wszystko, a następniezwraca listę.

+0

To nie było prawdziwe zachowanie, które chciałem, ale +1 dla dobrego wyjaśnienia. liftM ma teraz dużo więcej sensu. –

14

Nie sądzę, istnieje coś takiego jak takeWhileM w bibliotece standardowej, ale można napisać samemu, tak że tylko tyle IO jest realizowane w miarę potrzeb:

takeWhileM :: (Monad m) => (a -> Bool) -> [m a] -> m [a] 
takeWhileM _ [] = return [] 
takeWhileM p (a:as) = 
    do v <- a 
     if p v 
     then do vs <- takeWhileM p as 
       return (v:vs) 
     else return [] 

lista dostarczane jest oceniany tylko aż element zostanie znaleziony, że nie pasuje do predykat:

*Main> takeWhileM (<4) (map f [1..5]) 
1 
2 
3 
4 
[1,2,3] 
+1

Dla spójności z 'Control.Monad.filterM', oczekiwałbym typu bardziej podobnego do' takeWhileM :: (Monad m) => (a -> m Bool) -> [a] -> m [a] '; to sprawi, że użycie będzie podobne do 'join. sekwencja liftM. takeWhileM (liftM (<4)) '. Ale to, co OP chce, jest trochę brzydsze, więc. – ephemient

+0

@sth, @ephemient: nie w standardowej bibliotece, ale w moim nowo wydanym pakiecie "generator" (w hackage) istnieje bardziej ogólna funkcja "Take". twój takeWhileM potrzebuje listy działań, aby nie polegać na akcjach wewnątrz monady, podczas gdy generator pobiera Za listę, której monadą jest m, pozwala na to samo i więcej. – yairchu

+0

+1 za dokładnie pasujące zachowanie, które chciałem. Zaktualizowałem to pytanie, a ja poczekam i zobaczę, czy ktoś ma standardowe rozwiązanie lib. Jeśli go nie ma, akceptuję twoją odpowiedź. –

6

można użyć jednego z pakietu "List".

import Control.Monad.ListT (ListT) 
import Data.List.Class (execute, fromList, joinM, takeWhile) 
import Prelude hiding (takeWhile) 

f x = print x >> return x 
main = 
    execute . takeWhile (< 4) . 
    joinM $ fmap f (fromList [0..5] :: ListT IO Int) 
  • fromList [0..5] tworzy jednowartościowy listę zawierającą 0..5 która wykonuje żadnych czynności monadycznego
  • fmap f do tego wyniki wykaz w ListT IO (IO Int) który nadal wykonuje żadnych czynności monadycznego, zawiera tylko te.
  • joinM zamienia to w ListT IO Int. każde zawarte działanie zostanie wykonane, gdy element zostanie zużyty, a jego wynikiem będzie wartość na liście.
  • takeWhile jest generalizowany dla każdego List. Zarówno [], jak i "Monad m => ListT m" są instancjami List.
  • execute spożywa listę monadyczną, wykonując wszystkie jej działania.
  • Jeśli interesują Cię wyniki, możesz użyć "toList :: List m => m a -> ItemM m [a]" ("ItemM (ListT IO)" to IO). więc w tym przypadku jest to "toList :: ListT IO a -> IO [a]". Co więcej, można nadal używać funkcji wyższego rzędu, takich jak scanl, itp., Aby przetworzyć listę monadyczną w trakcie jej wykonywania.