2017-06-18 25 views
6

Zamieszczam poniżej pewien kod w Haskell. Proszę traktować kod jako przykład, który zamierzam wykorzystać, aby wyjaśnić, co chciałbym wiedzieć.Haskell: Zastosowanie funkcji do wszystkich kombinacji argumentów

try :: [[Char]] -> [[Char]] -> [[Char]] -> [[Char]] 
try (a:as) (b:bs) (c:cs) | ((checkIfCorrect a b c) == True) = a:b:[c] 
          | otherwise = try as bs cs 

checkIfCorrect :: [Char] -> [Char] -> [Char] -> Bool 
checkIfCorrect a b c = True 

Ostatecznie checkIfCorrect powraca True tylko dla jednej kombinacji argumentów. checkIfCorrect to naprawdę długa funkcja, więc postanowiłem zamieścić tutaj zastępcę. W powyższym przykładzie funkcja checkIfCorrect jest stosowana (przez funkcję try) do: pierwszej [Char] na pierwszej liście, najpierw [Char] na drugiej liście i pierwszej [Char] jednej trzeciej listy. Jeśli pierwsze zachowanie nie jest spełnione, funkcja checkIfCorrect zostanie zastosowana do: drugiej [Char] na pierwszej liście ... i tak dalej. To, co chciałbym osiągnąć, to zastosowanie funkcji checkIfCorrect (przez funkcję try) do wszystkich kombinacji [Char] s ze wszystkich list ([[Char]]). Mam na myśli następujące (np.): Trzecie [Char] na pierwszej liście, ósme [Char] na drugiej liście, jedenastą [Char] na trzeciej liście i tak dalej. Wszyscy ze wszystkimi. Jak mogłem to łatwo osiągnąć?

+0

Możesz chcieć zmienić 'try', aby typ zwracany był krotką 3 wartości zamiast listy. W ten sposób jest jasne, że oczekujesz dokładnie 3 wartości na liście. – 4castle

Odpowiedz

5

Tak, można je również wyglądają bardziej elegancki listowego:

try :: [[Char]] -> [[Char]] -> [[Char]] -> [[Char]] 
try as bs cs = head [ [a,b,c] | a <- as, b <- bs, c <- cs, checkIfCorrect a b c ] 
--     \__ __/ \__________ ____________/ \__________ _______/ 
--      v     v       v 
--      yield   enumeration     filter 

Kod działa w następujący sposób: w prawej części listy zrozumienie składa się o "wyliczenie "część (oznaczona w sekcji komentarzy). Ponieważ piszemy a <- as, b <- bs, c <- cs to znaczy, że a będzie przyjmować dowolną wartość od as, a dla każdy taki a, b podejmie jakąkolwiek wartość bs, itd. To znaczy, każda możliwa kombinacja będzie emitowany.

Następny jest faza „filtr”: jest orzecznikiem checkIfCorrect a b c która zostanie wywołana i tylko wtedy, gdy orzeczenie zwraca True, wynik będzie „dały”.

Po lewej stronie widzimy "plon". Opisuje, co dodać do listy (na podstawie wyliczenia), biorąc pod uwagę, że filtr się powiódł. Jeśli tak się stanie, dodajemy do tej listy [a,b,c]. Jeśli istnieje wiele takich konfiguracji, które się powiodą, możemy otrzymać listę zawierającą wiele rozwiązań. Należy jednak pamiętać, że lista rozumienie odbywa leniwie: tak długo, jak nie pytają o co najmniej jeden taki element, to nie będzie generować pierwszy element, ani drugi, itd

Teraz musimy również head (przed zrozumieniem listy). head :: [a] -> a zwraca element pierwszy z listy. Tak więc try zwróci pierwszy element, który spełnia warunek.

11

Po prostu chciałem pokazać ci alternatywny sposób pisania @WillemVanOnsem's code. Zgaduję, że jesteś początkującym Haskellem, więc miejmy nadzieję, że ta odpowiedź pozwoli ci rzucić okiem na bogaty i piękny pomysł, który wkrótce będziesz uczyć się w pełni, wraz z postępem w nauce języka.Więc nie martw się zbytnio, jeśli od razu nie zrozumiesz wszystkiego na temat tego kodu; Próbuję tylko dać ci posmakować!

Lista rozumienie zawsze można przeformułować używając listy monady:

import Control.Monad (guard) 

try as bs cs = head $ do 
    a <- as 
    b <- bs 
    c <- cs 
    guard $ checkIfCorrect a b c 
    return [a,b,c] 

używam do notacji jako specjalną notacją dla zagnieżdżonych pętlach: dla każdego a w as dla każdego b w bs , i dla każdego c w cs, otrzymujemy [a,b,c] jeśli checkIfCorrect zwraca True. Tłumaczenie z listy zrozumiałej jest proste: "wyliczenie" części listy zrozumienie zamienia się w "powiązania" przy użyciu <-, "filtr" zamienia się w połączenia z guard, a "wydajność" zamienia się w return s.

W imperatywem języku jak Python można napisać tak:

def try(as, bs, cs): 
    for a in as: 
     for b in bs: 
      for c in cs: 
       if checkIfCorrect(a, b, c): 
        yield [a,b,c] 

jak polityka w ramach Zachodniej neoliberalnej hegemonii, kod imperatyw stopniowo wkracza w prawo. Kod "Schody" w rzeczywistości tak często pojawia się w programowaniu imperatywnym (pomyśl o "piekle wywołania zwrotnego" w JS), więc wymyślono monady, aby pomóc przeciwdziałać tej tendencji. Okazały się tak przydatne, że wynaleziono dla nich specjalną składnię, mianowicie: do -notacja.

+5

Ten ostatni akapit jest cytowalny. :) – Alec

+0

'find checkIfCorrect (liftM3 (,,) as bs cs)' byłoby być może innym sposobem na zrobienie tego, ale musieliby nieco zmodyfikować typy ich funkcji. – 4castle

+0

zamiast 'yield', ale' return', zwraca tylko kombinację * first *, która przechodzi test. –

3

Chociaż zarówno odpowiedzi Willema Van Onsem, jak i The Orgazoid są dobre (awansowane), można również podejść do części problemu w bardziej ogólny sposób, a nie tylko w przypadku list.

dla następujących, będziesz potrzebował tych importu:

import Control.Monad (MonadPlus, mfilter) 
import Data.Maybe (fromMaybe, listToMaybe) 

Jeśli mam zrozumienia pytanie poprawnie, chcesz wypróbować wszystkie kombinacje as, bs i cs. zazwyczaj można osiągnąć podobny zachowanie skojarzone z Applicative typeclass:

combinations = (,,) <$> as <*> bs <*> cs 

(,,) to funkcja, która tworzy trójek (krotki trzech elementów) z trzech pojedynczych wartości.

To działa na listach, bo listy są aplikacyjnych:

*Prelude> (,,) <$> [1,2] <*> ["foo", "bar"] <*> [True, False] 
[(1,"foo",True),(1,"foo",False),(1,"bar",True),(1,"bar",False),(2,"foo",True),(2,"foo",False),(2,"bar",True),(2,"bar",False)] 

ale działa także na przykład Maybe s:

*Prelude> (,,) <$> Just 1 <*> Just "foo" <*> Just False 
Just (1,"foo",False) 

Z tym, można teraz określić istotę swojej funkcji:

try' :: MonadPlus m => ((a, a, a) -> Bool) -> m a -> m a -> m a -> m [a] 
try' predicate as bs cs = 
    tripleToList <$> mfilter predicate combinations 
    where 
    combinations = (,,) <$> as <*> bs <*> cs 
    tripleToList (a, b, c) = [a, b, c] 

zauważysz, że ta funkcja jest całkowicie generic pomocnika. Działa dla dowolnej instancji MonadPlus dowolnego zawartego elementu a.

Oto kilka przykładów:

*Answer> try' (const True) ["foo", "bar", "baz"] ["qux", "quux", "quuz", "corge"] ["grault", "garply"] 
[["foo","qux","grault"],["foo","qux","garply"],["foo","quux","grault"],["foo","quux","garply"],["foo","quuz","grault"],["foo","quuz","garply"],["foo","corge","grault"],["foo","corge","garply"],["bar","qux","grault"],["bar","qux","garply"],["bar","quux","grault"],["bar","quux","garply"],["bar","quuz","grault"],["bar","quuz","garply"],["bar","corge","grault"],["bar","corge","garply"],["baz","qux","grault"],["baz","qux","garply"],["baz","quux","grault"],["baz","quux","garply"],["baz","quuz","grault"],["baz","quuz","garply"],["baz","corge","grault"],["baz","corge","garply"]] 
*Answer> try' (const False) ["foo", "bar", "baz"] ["qux", "quux", "quuz", "corge"] ["grault", "garply"] 
[] 
*Answer> try' (const True) (Just "foo") (Just "bar") (Just "baz") 
Just ["foo","bar","baz"] 
*Answer> try' (const False) (Just "foo") (Just "bar") (Just "baz") 
Nothing 

Należy zauważyć, że jeśli predicate zawsze zwraca False, dostaniesz nic z powrotem.Dla list otrzymasz pustą listę; dla Maybe dosłownie dostajesz Nothing.

Do tej pory wszystko jest ogólne, ale checkIfCorrect nie jest. Wygląda na to, że chcesz uzyskać tylko pierwsze pasujące elementy. Można to osiągnąć poprzez komponowanie try' z checkIfCorrect:

try :: [String] -> [String] -> [String] -> [String] 
try as bs cs = fromMaybe [] $ listToMaybe $ try' isCorrect as bs cs 
    where isCorrect (a, b, c) = checkIfCorrect a b c 

Tutaj Utworzyłem prywatną isCorrect funkcji w celu uncurry funkcję checkIfCorrect. Następnie użyłem kombinacji listToMaybe i fromMaybe, aby zwrócić pierwszy element wynikowej listy. Inne odpowiedzi tutaj używają head, ale to rzuci wyjątek, jeśli lista jest pusta, więc użyłem tego połączenia, ponieważ jest to bezpieczne.

+0

lub "combined = liftA3 (,,) as bs cs' –

Powiązane problemy