come modellare ADN e ARN in haskell?

3

Vorrei modellare alcune catene nucleotidiche: ADN & ARN. ADN è un elenco di nucleotidi: A, T, G, C. ARN è una lista di nucleotidi: A, U, G, C.

idealmente, vorrei definire per es. ADN come elenco di tipi di dati A, T, G, C.
Ho questo codice, che funziona ma non è sufficiente:

data NtADN = Td | Ad | Cd | Gd deriving (Eq)
    data NtARN = Ur | Ar | Cr | Gr deriving (Eq)
    data ADN = ADN [NtADN] deriving (Eq)
    data ARN = ARN [NtARN] deriving (Eq)


    class NucleotidChain a where
        valid :: a -> Bool
        countACGX :: a -> (Int,Int,Int,Int)

ma non ne sono soddisfatto: i nucleotidi sono dichiarati 2 volte con suffissi arbitrari (Ad, Ar ...)

inoltre, countACGX, che conta il numero di ciascun nucleotide in ADN e amp; Gli ARN devono essere dichiarati 2 volte, uno per ADN e uno per ARN:

instance NucleotidChain ADN where
        valid (ADN s) = all (\t->(t==Ad)|| (t==Td)||(t==Cd)||(t==Gd)) s 
        countACGX (ADN s) = 
            let     a= length $ elemIndices Ad s 
                    c= length $ elemIndices Cd s 
                    g= length $ elemIndices Gd s 
                    t= length $ elemIndices Td s 
            in (a,c,g,t) 

    instance NucleotidChain ARN where
        valid (ARN s) = all (\t-> (t==Ur) || (t==Ar) || (t==Cr) || (t==Gr)) s
        countACGX (ARN s) = 
            let     a= length $ elemIndices Ar s 
                    c= length $ elemIndices Cr s 
                    g= length $ elemIndices Gr s 
                    u= length $ elemIndices Ur s 
            in (a,c,g,u) 

c'è un modo per sbarazzarsi di questa duplicazione? per dichiarare solo 5 nucleotidi (A, T, G, C, U) e soprattutto perché non avere successo nel dichiarare un dato ADN (e ARN) come una matrice di elementi preso nel risultato di una funzione diversa per ADN (& ARN)? come ad esempio:

data Nt = A | T | G | C | U
data ADN = ADN [nts]
data ARN = ARN [nts]

class NtChain a where
  nts :: [Nt]
    
posta lolveley 08.01.2017 - 12:40
fonte

2 risposte

2

Il tuo modello attuale è buono perché mantiene i simboli in ADN & Le stringhe ARN sono completamente separate: non è possibile inserire accidentalmente U in una stringa ADN perché fa parte di un tipo diverso. Teniamolo.

Da ciò segue che la tua funzione valid è superflua perché è sempre garantito il true - il sistema di tipi non consentirà una stringa non valida.

Se vuoi scrivere una stringa ADN come [t,a,g,c] invece di [Td,Ad,Cd,Gd] , possiamo usare una classe tipo per definire una funzione nullo polimorfica:

class ProvidesT a where t :: a
class ProvidesA a where a :: a
class ProvidesG a where g :: a
class ProvidesC a where c :: a
class ProvidesU a where u :: a

Quindi possiamo fornire istanze per NtADN :

instance ProvidesT NtADN where t = Td

e così via per tutti i casi. Fare questo per nomi di metodi così brevi non è una buona idea, perché potrebbero essere accidentalmente ombreggiati da variabili locali. (Potrebbe essere possibile dichiarare un costruttore di tipi all'interno della classe con l'estensione "famiglie di caratteri", ma non ho esperienza con questo).

È noioso? Sì, ma questo è Haskell. Puoi prendere in considerazione l'utilizzo del modello Haskell per automatizzare le definizioni dell'istanza, ma potrebbe essere più utile della semplice spiegazione di tutto.

Il tuo metodo countACGX non è un difetto di progettazione, sebbene l'implementazione possa essere raffinata in modo sostanziale. Definisci count symbol = length $ elemIndices symbol s come helper, quindi la funzione si semplifica in (count Ad, count Cd, ...) .

    
risposta data 08.01.2017 - 15:10
fonte
2

Ecco un altro modo per definire un elenco semplice

{-# LANGUAGE EmptyDataDecls #-}
module Lib
    ( Nucleotide
    , DNA
    , RNA
    , NucleotideType
    , a, t, g, c, u
    ) where

data Nucleotide a = T | A | G | C | U deriving Eq

data DNA
data RNA

class NucleotideType a where
  switch :: f DNA -> f RNA -> f a

instance NucleotideType DNA where
  switch = \ dna _ -> dna

instance NucleotideType RNA where
  switch = \ _ rna -> rna

a :: (NucleotideType t) => Nucleotide t
a = A

t :: Nucleotide DNA
t = T

u :: Nucleotide RNA
u = U

g :: (NucleotideType t) => Nucleotide t
g = G

c :: (NucleotideType t) => Nucleotide t
c = C

L'idea è che usiamo tipi fantasma per contrassegnare ogni nucleotide come DNA o RNA (dichiarando una famiglia di tipi "chiusi" per comprenderli) e utilizzare costruttori intelligenti per assicurarsi che i consumatori del nostro modulo possano solo fare t come DNA e u come RNA. (Nota che non dobbiamo esportare i normali costruttori.)

Possiamo quindi deduplicare la funzione count come

countNucleotideBase :: [Nucleotide t] -> (Int, Int, Int, Int, Int)
countNucleotideBase xs =
  let [a, t, c, g, u] = map (\n -> length . elemIndices n $ xs) [A, T, C, G, U]
  in (a, t, c, g, u)

countNucleotideDNA :: [Nucleotide DNA] -> (Int, Int, Int, Int)
countNucleotideDNA xs = 
  let (a, t, c, g, u) = countNucleotideBase xs
  in (a, t, c, g)

Come risposta diversa che si basa sulla sicurezza del vero tipo piuttosto che sull'incapsulazione, possiamo usare GADT per codificare veramente che T è solo DNA mentre G è o DNA o RNA , diciamo, noi posso dire

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DataKinds #-}
module Lib
    ( NT(..)
    , N(..)
    , countDNA
    ) where

import Data.Kind(Type)

data NT = DNA | RNA

data N :: NT -> Type where
  T :: N 'DNA
  U :: N 'RNA
  C :: N  t
  G :: N  t
  A :: N  t

La cosa bella di questo è che possiamo esporre direttamente i costruttori di dati e ottenere ancora la sicurezza completa del tipo (ad esempio, l'espressione [T, C, U] è mal tipata). Il problema qui è che come la tua prima soluzione non possiamo generalizzare oltre N 'DNA e N 'RNA . Per fare ciò, possiamo usare i tipi esistenziali per nascondere quella fastidiosa variabile.

data W = forall t. W (N t)

countBase :: [W] -> (Int, Int, Int, Int, Int) 
countBase ws =
  let [t, u, c, g, a] = map (\ w -> length . filter (== w) $ ws) [W T, W U, W C, W G, W A]
  in (t, u, c, g, a)

instance Eq W where
  (W T) == (W T) = True
  (W U) == (W U) = True
  (W C) == (W C) = True
  (W G) == (W G) = True
  (W A) == (W A) = True
  _     == _     = False

Questo comporta alcuni problemi e alcuni problemi di performance in uso da usare queste funzioni generali che dobbiamo avvolgere e potenzialmente scartare i nostri argomenti in W . per es.

countDNA :: [N 'DNA] -> (Int, Int, Int, Int)
countDNA xs =
  let (t, _, c, g, a) = countBase (map W xs)
  in (t, c, g, a)

Anche se l'ultimo di questi è probabilmente un po 'più vicino a come un guru Haskell (che io certamente non sono) potrebbe risolvere questo problema, ha seri svantaggi: mentre le soluzioni possono raggiungere il typesafety pur essendo relativamente concise, lo comprano ad un molta complessità Se tutti quelli che lavorano sulla base di codice sono a loro agio con tutte le ultime estensioni di GHC e la programmazione quasi dipendente che consentono, prendi il comando. Questo descrive una piccola percentuale anche di persone che conoscono alcuni Haskell e potrebbero infine danneggiare il tuo progetto (di nuovo, a seconda di chi saranno i tuoi collaboratori o /); in questi casi, cercherò meccanismi come costruttori intelligenti e classi di tipi che forniscano almeno una sicurezza di runtime per una barriera molto più bassa all'ingresso.

    
risposta data 08.01.2017 - 18:48
fonte

Leggi altre domande sui tag