2017-09-09 3 views
2

Je fais une tâche scolaire où on me donne un petit échantillon de code que je peux utiliser plus tard. Je comprends 90% de ce code mais il y a une petite ligne/fonction que pour la vie de moi je n'arrive pas à comprendre ce que ça fait (je suis très nouveau chez Haskell btw).Haskell - J'ai de la difficulté à comprendre un petit bout de code

Exemple de code:

data Profile = Profile {matrix::[[(Char,Int)]], moleType::SeqType, nrOfSeqs::Int, nm::String} deriving (Show) 

nucleotides = "ACGT" 
aminoacids = sort "ARNDCEQGHILKMFPSTWYVX" 

makeProfileMatrix :: [MolSeq] -> [[(Char, Int)]] 
makeProfileMatrix [] = error "Empty sequence list" 
makeProfileMatrix sl = res 
    where 
    t = seqType (head sl) 
    defaults = 
     if (t == DNA) then 
     zip nucleotides (replicate (length nucleotides) 0) -- Row 1 
     else 
     zip aminoacids (replicate (length aminoacids) 0) -- Row 2 
    strs = map seqSequence sl        -- Row 3 
    tmp1 = map (map (\x -> ((head x), (length x))) . group . sort) 
       (transpose strs)       -- Row 4 
    equalFst a b = (fst a) == (fst b) 
    res = map sort (map (\l -> unionBy equalFst l defaults) tmp1) 

{-Row 1: 'replicate' creates a list of zeros that is equal to the length of the 'nucleotides' string. 
This list is then 'zipped' (combines each element in each list into pairs/tuples) with the nucleotides-} 

{-Row 2: 'replicate' creates a list of zeros that is equal to the length of the 'aminoacids' string. 
This list is then 'zipped' (combines each element in each list into pairs/tuples) with the aminoacids-} 

{-Row 3: The function 'seqSequence' is applied to each element in the 'sl' list and then returns a new altered list. 
In other words 'strs' becomes a list that contains the all the sequences in 'sl' (sl contains MolSeq objects, not strings)-} 

{-Row 4: (transpose strs) creates a list that has each 'column' of sequences as a element (the first element is made up of each first element in each sequence etc.). 
--} 

J'ai écrit une explication pour chaque ligne marquée dans le code (que je pense à ce jour est correct) mais je suis bloqué lorsque je tente de comprendre ce qui fait la ligne 4. Je comprends le bit 'transposer' mais je n'arrive pas à comprendre ce que fait la fonction carte interne. Pour autant que je sache, une fonction 'map' a besoin d'une liste en tant que second paramètre pour fonctionner, mais la fonction map interne n'a qu'une fonction anonyme mais pas de liste à utiliser. Pour être parfaitement clair, je ne comprends pas ce que fait toute la ligne intérieure map (\x -> ((head x), (length x))) . group . sort. S'il vous plaît aider!

Bonus !:

Voici un autre morceau de code exemple que je ne peux pas comprendre (jamais travaillé avec des classes en Haskell):

class Evol object where 
name :: object -> String 
distance :: object -> object -> Double 
distanceMatrix :: [object] -> [(String, String, Double)] 
addRow :: [object] -> Int -> [(String, String, Double)] 
distanceMatrix [] = [] 
distanceMatrix object = 
    addRow object 0 ++ distanceMatrix (tail object) 
addRow object num -- Adds row to distance matrix 
    | num < length object = (name a, name b, distance a b) : addRow object (num + 1) 
    | otherwise = [] 
    where 
     a = head object 
     b = object !! num 


-- Determines the name and distance of an instance of "Evol" if the instance is a "MolSeq". 
instance Evol MolSeq where 
name = seqName 
distance = seqDistance 

-- Determines the name and distance of an instance of "Evol" if the instance is a "Profile". 
instance Evol Profile where 
name = profileName 
distance = profileDistance 

Surtout cette partie:

addRow object num -- Adds row to distance matrix 
    | num < length object = (name a, name b, distance a b) : addRow object (num + 1) 
    | otherwise = [] 
    where 
     a = head object 
     b = object !! num 

Vous n'avez pas besoin d'expliquer celui-ci si vous ne voulez pas que je sois un peu confus quant à ce que 'addRow' essaie réellement de faire (en détail).

Merci!

+1

Il est cool, que vous Je m'intéresse à Haskell, mais il vaudrait mieux extraire la partie «bonus» en une question distincte, parce que ces deux questions ne sont pas liées les unes aux autres. –

Répondre

5

map (\x -> (head x, length x)) . group . sort est une manière idiomatique de générer un histogramme. Quand vous voyez quelque chose comme ça que vous ne comprenez pas, essayez de le décomposer en petits morceaux et de les tester sur les entrées d'échantillons:

(\x -> (head x, length x)) "AAAA" 
-- ('A', 4) 

(group . sort) "CABABA" 
-- ["AAA", "BB", "C"] 

(map (\x -> (head x, length x)) . group . sort) "CABABA" 
map (\x -> (head x, length x)) (group (sort "CABABA")) 
-- [('A', 3), ('B', 2), ('C', 1)] 

comme une composition de 3 fonctions Il est écrit dans style de point sans, map (…), group et sort, mais pourrait aussi être écrit comme lambda:

\row -> map (…) (group (sort row)) 

Pour chaque ligne de la matrice transposée, elle produit un histogramme des données dans cette ligne. Vous pouvez obtenir une représentation plus visuelle de ce par le formatage et l'imprimer:

let 
    showHistogramRow row = concat 
    [ show $ head row 
    , ":\t" 
    , replicate (length row) '#' 
    ] 
    input = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] 

putStr 
    $ unlines 
    $ map showHistogramRow 
    $ group 
    $ sort input 

-- 1: ## 
-- 2: # 
-- 3: ## 
-- 4: # 
-- 5: ### 
-- 6: # 
-- 9: # 

Quant à ceci:

addRow object num -- Adds row to distance matrix 
    | num < length object = (name a, name b, distance a b) : addRow object (num + 1) 
    | otherwise = [] 
    where 
     a = head object 
     b = object !! num 

addRow fait une liste des distances du premier élément object à chaque des autres éléments. Il utilise l'indexation dans la liste dans une sorte de non évidente, lorsqu'un plus simple et plus idiomatiques map suffirait:

addRow object = map (\ b -> (name a, name b, distance a b)) object 
    where a = head object 

Ordinairement, il est bon d'éviter partielles des fonctions telles que head car ils peuvent jeter une exception sur certaines entrées (par exemple head []). Ici c'est bien, car si la liste d'entrée est vide, alors a ne sera jamais utilisé, et donc head ne sera jamais appelé.

distanceMatrix pourrait être exprimé avec un map aussi bien, parce qu'il est juste d'appeler une fonction (addRow) sur tous les tails de la liste et les concaténant avec ++:

distanceMatrix object = concatMap addRow (tails object) 

Cela pourrait être écrit au point -Free style aussi. \x -> f (g x) peut être écrit simplement f . g; ici, f est concatMap addRow et g est tails:

distanceMatrix = concatMap addRow . tails 

Evol décrit simplement l'ensemble des types pour lesquels vous pouvez générer un distanceMatrix, y compris MolSeq et Profile.Notez que addRow et distanceMatrix ne doivent pas nécessairement être membres de cette classe, parce qu'ils sont mis en œuvre entièrement en termes de name et distance, de sorte que vous pouvez les passer au niveau supérieur:

distanceMatrix :: (Evol object) => [object] -> [(String, String, Double)] 
distanceMatrix = concatMap addRow . tails 

addRow :: (Evol object) => [object] -> Int -> [(String, String, Double)] 
addRow object = map (\ b -> (name a, name b, distance a b)) object 
    where a = head object 
+0

Merci pour cette réponse détaillée! Maintenant, je comprends! :) – Schytheron

3

la fonction de carte interne a seulement une fonction anonyme, mais aucune liste pour fonctionner sur

Étant donné qu'il ya une fonction f de type a -> b -> c, qui prend deux arguments et retourne une valeur de type c. Si le f est appelé avec un paramètre, il renvoie une autre fonction de type b -> c, qui va prendre un paramètre de plus et retourner une valeur. C'est ce qu'on appelle la curry.

Cette ligne:

map (map (\x -> ((head x), (length x))) . group . sort) (transpose strs) 

peut être transformé en:

map (\str -> (map (\x -> ((head x), (length x))) . group . sort) str)(transpose strs) 

Sous cette forme, il peut être effacé, qu'il ya en fait une liste pour opérer.

Cette fonction

(map (\x -> ((head x), (length x))) . group . sort) 

est juste une composition de sort, group et map (\x -> ((head x), (length x))).

Voyons voir comment cela fonctionne sur [2,1,1,1,4]:

sort [2, 1, 1, 1, 4] =>[1, 1, 1, 2, 4]

group [1, 1, 1, 2, 4] =>[[1,1,1],[2],[4]]

map (\x -> ((head x), (length x))) =>[(1,3),(2,1),(4,1)]

Il retourne juste une liste de tuples. Chaque tuple contient un élément en tant que premier élément et le nombre d'occurrences en tant que second élément.