diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -2,7 +2,14 @@
 # Changelog for ELynx
 
 
-## Version 0.3.1; in development
+## Version 0.3.2; in development
+
+
+## Version 0.3.1
+
+-   Use Attoparsec.
+-   Use ByteString consistently.
+-   Remove elynx-tools dependency from libaries.
 
 
 ## Version 0.3.0
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 # The ELynx Suite
 
-Version: 0.3.0.
+Version: 0.3.1.
 Reproducible evolution made easy.
 
 <p align="center"><img src="https://travis-ci.org/dschrempf/elynx.svg?branch=master"/></p>
@@ -90,9 +90,9 @@
 
     slynx --help | head -n -16
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx [-v|--verbosity VALUE] [-o|--output-file-basename NAME] 
                  [-f|--force] COMMAND
@@ -136,9 +136,9 @@
 
     slynx concatenate --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx concatenate (-a|--alphabet NAME) INPUT-FILE
       Concatenate sequences found in input files.
@@ -157,9 +157,9 @@
 
     slynx examine --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx examine (-a|--alphabet NAME) INPUT-FILE [--per-site]
       Examine sequences. If data is a multi sequence alignment, additionally analyze columns.
@@ -179,9 +179,9 @@
 
     slynx filter-rows --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx filter-rows (-a|--alphabet NAME) INPUT-FILE [--longer-than LENGTH] 
                              [--shorter-than LENGTH] [--standard-characters]
@@ -202,9 +202,9 @@
 
     slynx filter-columns --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx filter-columns (-a|--alphabet NAME) INPUT-FILE 
                                 [--standard-chars DOUBLE]
@@ -226,9 +226,9 @@
 
     slynx simulate --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx simulate (-t|--tree-file Name) [-s|--substitution-model MODEL] 
                           [-m|--mixture-model MODEL] [-e|--edm-file NAME] 
@@ -305,9 +305,9 @@
 
     slynx sub-sample --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx sub-sample (-a|--alphabet NAME) INPUT-FILE
                             (-n|--number-of-sites INT)
@@ -335,9 +335,9 @@
 
     slynx translate --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: slynx translate (-a|--alphabet NAME) INPUT-FILE (-r|--reading-frame INT)
                            (-u|--universal-code CODE)
@@ -360,9 +360,9 @@
 
     tlynx --help | head -n -16
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: tlynx [-v|--verbosity VALUE] [-o|--output-file-basename NAME] 
                  [-f|--force] COMMAND
@@ -399,9 +399,9 @@
 
     tlynx compare --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: tlynx compare [-n|--normalize] [-b|--bipartitions] [-t|--intersect] 
                          [-f|--newick-format FORMAT] NAMES
@@ -428,9 +428,9 @@
 
     tlynx examine --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: tlynx examine INPUT-FILE [-f|--newick-format FORMAT]
       Compute summary statistics of phylogenetic trees.
@@ -452,9 +452,9 @@
 
     tlynx simulate --help
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: tlynx simulate [-t|--nTrees INT] [-n|--nLeaves INT] [-H|--height DOUBLE] 
                           [-M|--condition-on-mrca] [-l|--lambda DOUBLE] 
@@ -491,9 +491,9 @@
 
     elynx --help | head -n -16
 
-    ELynx Suite version 0.3.0.
+    ELynx Suite version 0.3.1.
     Developed by Dominik Schrempf.
-    Compiled on July 30, 2020, at 12:40 pm, UTC.
+    Compiled on August 3, 2020, at 12:12 pm, UTC.
     
     Usage: elynx COMMAND
       Validate and redo past ELynx analyses
diff --git a/elynx-seq.cabal b/elynx-seq.cabal
--- a/elynx-seq.cabal
+++ b/elynx-seq.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: afdff7b344d4b63f734a6f18db88ef39b1f45ed84c64e63045b57edfb839684f
+-- hash: 656a2c088bc0e776325be75f926d33618cc4a11940ba39f35f50bb90e6636b7d
 
 name:           elynx-seq
-version:        0.3.0
+version:        0.3.1
 synopsis:       Handle molecular sequences
 description:    Examine, modify, and simulate molecular sequences in a reproducible way. Please see the README on GitHub at <https://github.com/dschrempf/elynx>.
 category:       Bioinformatics
@@ -56,12 +56,11 @@
   ghc-options: -Wall -fllvm
   build-depends:
       aeson
+    , attoparsec
     , base >=4.7 && <5
     , bytestring
     , containers
-    , elynx-tools
     , matrices
-    , megaparsec
     , mwc-random
     , parallel
     , primitive
@@ -90,7 +89,6 @@
     , elynx-seq
     , elynx-tools
     , hspec
-    , hspec-megaparsec
     , matrices
     , vector
   default-language: Haskell2010
diff --git a/src/ELynx/Data/Alphabet/Character.hs b/src/ELynx/Data/Alphabet/Character.hs
--- a/src/ELynx/Data/Alphabet/Character.hs
+++ b/src/ELynx/Data/Alphabet/Character.hs
@@ -26,11 +26,11 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import qualified Data.Vector.Unboxed as V
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Alphabet characters; abstracted so that representation can be changed at
 -- some point.
diff --git a/src/ELynx/Data/Alphabet/DistributionDiversity.hs b/src/ELynx/Data/Alphabet/DistributionDiversity.hs
--- a/src/ELynx/Data/Alphabet/DistributionDiversity.hs
+++ b/src/ELynx/Data/Alphabet/DistributionDiversity.hs
@@ -33,8 +33,17 @@
 import qualified Data.Vector.Generic as V
 import ELynx.Data.Alphabet.Alphabet
 import ELynx.Data.Alphabet.Character
-import ELynx.Tools
 
+eps :: Double
+eps = 1e-12
+
+-- Calculate x*log(x) but set to 0.0 when x is smaller than 'eps'.
+xLogX :: Double -> Double
+xLogX x
+  | x < 0.0 = error "Argument lower than zero."
+  | eps > x = 0.0
+  | otherwise = x * log x
+
 -- | Entropy of vector.
 entropy :: (Vector v Double) => v Double -> Double
 entropy v =
@@ -44,7 +53,7 @@
         ("entropy: Sesult of following vector is NaN: " ++ show (toList v) ++ ".")
     else res
   where
-    res = negate $ sumVec $ V.map xLogX v
+    res = negate $ V.sum $ V.map xLogX v
 
 -- | Effective number of used characters measured using 'entropy'. The result
 -- only makes sense when the sum of the array is 1.0.
@@ -55,7 +64,7 @@
 -- binomially sampling the same character twice and only makes sense when the
 -- sum of the array is 1.0.
 homoplasy :: Vector v Double => v Double -> Double
-homoplasy v = sumVec $ V.map (\x -> x * x) v
+homoplasy v = V.sum $ V.map (\x -> x * x) v
 
 -- | Effective number of used characters measured using 'homoplasy'. The result
 -- only makes sense when the sum of the array is 1.0.
@@ -98,4 +107,4 @@
 frequencyCharacters alph d = V.map (`saveDivision` s) counts
   where
     counts = countCharacters alph d
-    s = sumVec counts
+    s = V.sum counts
diff --git a/src/ELynx/Data/Character/AminoAcid.hs b/src/ELynx/Data/Character/AminoAcid.hs
--- a/src/ELynx/Data/Character/AminoAcid.hs
+++ b/src/ELynx/Data/Character/AminoAcid.hs
@@ -47,10 +47,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Amino acids.
 data AminoAcid = A | C | D | E | F | G | H | I | K | L | M | N | P | Q | R | S | T | V | W | Y
diff --git a/src/ELynx/Data/Character/AminoAcidI.hs b/src/ELynx/Data/Character/AminoAcidI.hs
--- a/src/ELynx/Data/Character/AminoAcidI.hs
+++ b/src/ELynx/Data/Character/AminoAcidI.hs
@@ -68,10 +68,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Amino acids.
 data AminoAcidI
diff --git a/src/ELynx/Data/Character/AminoAcidS.hs b/src/ELynx/Data/Character/AminoAcidS.hs
--- a/src/ELynx/Data/Character/AminoAcidS.hs
+++ b/src/ELynx/Data/Character/AminoAcidS.hs
@@ -52,10 +52,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Amino acids.
 data AminoAcidS
diff --git a/src/ELynx/Data/Character/AminoAcidX.hs b/src/ELynx/Data/Character/AminoAcidX.hs
--- a/src/ELynx/Data/Character/AminoAcidX.hs
+++ b/src/ELynx/Data/Character/AminoAcidX.hs
@@ -52,10 +52,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Amino acids.
 data AminoAcidX
diff --git a/src/ELynx/Data/Character/Character.hs b/src/ELynx/Data/Character/Character.hs
--- a/src/ELynx/Data/Character/Character.hs
+++ b/src/ELynx/Data/Character/Character.hs
@@ -27,10 +27,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import qualified Data.Set as S
 import Data.Vector.Unboxed.Base (Unbox)
 import Data.Word8 (Word8)
-import ELynx.Tools
 
 -- XXX: Remove name clash with ELynx.Data.Alphabet.Alphabet.Character?
 
diff --git a/src/ELynx/Data/Character/Nucleotide.hs b/src/ELynx/Data/Character/Nucleotide.hs
--- a/src/ELynx/Data/Character/Nucleotide.hs
+++ b/src/ELynx/Data/Character/Nucleotide.hs
@@ -29,10 +29,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Nucleotides.
 data Nucleotide = A | C | G | T
diff --git a/src/ELynx/Data/Character/NucleotideI.hs b/src/ELynx/Data/Character/NucleotideI.hs
--- a/src/ELynx/Data/Character/NucleotideI.hs
+++ b/src/ELynx/Data/Character/NucleotideI.hs
@@ -58,10 +58,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | NucleotideIs.
 data NucleotideI
diff --git a/src/ELynx/Data/Character/NucleotideX.hs b/src/ELynx/Data/Character/NucleotideX.hs
--- a/src/ELynx/Data/Character/NucleotideX.hs
+++ b/src/ELynx/Data/Character/NucleotideX.hs
@@ -34,10 +34,10 @@
   )
 where
 
+import Data.ByteString.Internal (c2w, w2c)
 import Data.Vector.Unboxed.Deriving
 import Data.Word8
 import qualified ELynx.Data.Character.Character as C
-import ELynx.Tools
 
 -- | Extended nucleotides.
 data NucleotideX
diff --git a/src/ELynx/Data/Sequence/Alignment.hs b/src/ELynx/Data/Sequence/Alignment.hs
--- a/src/ELynx/Data/Sequence/Alignment.hs
+++ b/src/ELynx/Data/Sequence/Alignment.hs
@@ -44,7 +44,8 @@
 
 import Control.Monad hiding (join)
 import Control.Monad.Primitive
-import qualified Data.ByteString.Lazy.Char8 as L
+import Control.Parallel.Strategies
+import qualified Data.ByteString.Lazy.Char8 as BL
 import Data.List hiding
   ( concat,
     length,
@@ -56,7 +57,6 @@
 import qualified ELynx.Data.Alphabet.DistributionDiversity as D
 import ELynx.Data.Sequence.Defaults
 import qualified ELynx.Data.Sequence.Sequence as S
-import ELynx.Tools
 import System.Random.MWC
 import Prelude hiding
   ( concat,
@@ -94,6 +94,8 @@
     a = S.alphabet $ head ss
     bss = map S.characters ss
     d = M.fromRows bss
+    allEqual [] = True
+    allEqual xs = all (== head xs) $ tail xs
 
 -- | Conversion to list of 'S.Sequence's.
 toSequences :: Alignment -> [S.Sequence]
@@ -106,32 +108,25 @@
   where
     rows = M.toRows da
 
--- -- | Show a 'Alignment', untrimmed.
--- summarizeSeq :: Alignment -> Int -> L.ByteString
--- summarizeSeq m i =
---   L.unwords [ alignLeft nameWidth $ names m !! i
---             , summarizeByteString summaryLength $
---               L.pack $ V.toList $ V.map toChar $ M.takeRow (matrix m) i ]
-
-header :: Alignment -> L.ByteString
+header :: Alignment -> BL.ByteString
 header a =
-  L.unlines $
-    [ L.pack "Multi sequence alignment.",
-      L.pack $ "Code: " ++ A.alphabetDescription (alphabet a) ++ ".",
-      L.pack $ "Length: " ++ show (length a) ++ "."
+  BL.unlines $
+    [ BL.pack "Multi sequence alignment.",
+      BL.pack $ "Code: " ++ A.alphabetDescription (alphabet a) ++ ".",
+      BL.pack $ "Length: " ++ show (length a) ++ "."
     ]
       ++ reportLengthSummary
       ++ reportNumberSummary
   where
     reportLengthSummary =
-      [ L.pack $
+      [ BL.pack $
           "For each sequence, the "
             ++ show summaryLength
             ++ " first bases are shown."
         | length a > summaryLength
       ]
     reportNumberSummary =
-      [ L.pack $
+      [ BL.pack $
           show summaryNSequences
             ++ " out of "
             ++ show (nSequences a)
@@ -140,9 +135,23 @@
       ]
 
 -- | Similar to 'S.summarizeSequenceList' but with different Header.
-summarize :: Alignment -> L.ByteString
+summarize :: Alignment -> BL.ByteString
 summarize a = header a <> S.body (toSequences a)
 
+-- Vertical concatenation.
+(===) :: V.Unbox a => M.Matrix a -> M.Matrix a -> M.Matrix a
+(===) l r = M.fromRows $ lRs ++ rRs
+  where
+    lRs = M.toRows l
+    rRs = M.toRows r
+
+-- Horizontal concatenation.
+(|||) :: V.Unbox a => M.Matrix a -> M.Matrix a -> M.Matrix a
+(|||) l r = M.fromColumns $ lCs ++ rCs
+  where
+    lCs = M.toColumns l
+    rCs = M.toColumns r
+
 -- | Join two 'Alignment's vertically. That is, add more sequences
 -- to an alignment. See also 'concat'.
 join :: Alignment -> Alignment -> Alignment
@@ -219,6 +228,16 @@
 -- the number of characters.
 type FrequencyData = M.Matrix Double
 
+-- Map a function on each row of a DIM2 array; parallel version with given chunk size.
+fMapColParChunk ::
+  (V.Unbox a, V.Unbox b) =>
+  Int ->
+  (V.Vector a -> V.Vector b) ->
+  M.Matrix a ->
+  M.Matrix b
+fMapColParChunk n f m =
+  M.fromColumns (map f (M.toColumns m) `using` parListChunk n rseq)
+
 -- | Calculcate frequency of characters at each site of a multi sequence alignment.
 toFrequencyData :: Alignment -> FrequencyData
 toFrequencyData a = fMapColParChunk 100 (D.frequencyCharacters spec) (matrix a)
@@ -236,6 +255,13 @@
   where
     nSites = M.cols fd
 
+-- Parallel map with given chunk size.
+parMapChunk :: Int -> (a -> b) -> [a] -> [b]
+parMapChunk n f as = map f as `using` parListChunk n rseq
+
+chunksize :: Int
+chunksize = 500
+
 -- | Diversity analysis. See 'kEffEntropy'.
 kEffEntropy :: FrequencyData -> [Double]
 kEffEntropy fd = parMapChunk chunksize D.kEffEntropy (M.toColumns fd)
@@ -262,6 +288,11 @@
 countUnknowns a = V.length . V.filter (A.isUnknown (alphabet a)) $ allChars
   where
     allChars = M.flatten $ matrix a
+
+-- Sample the given sites from a matrix.
+subSampleMatrix :: V.Unbox a => [Int] -> M.Matrix a -> M.Matrix a
+subSampleMatrix is m =
+  M.fromColumns $ foldl' (\a i -> M.takeColumn m i : a) [] is
 
 -- | Sample the given sites from a multi sequence alignment.
 subSample :: [Int] -> Alignment -> Alignment
diff --git a/src/ELynx/Data/Sequence/Sequence.hs b/src/ELynx/Data/Sequence/Sequence.hs
--- a/src/ELynx/Data/Sequence/Sequence.hs
+++ b/src/ELynx/Data/Sequence/Sequence.hs
@@ -48,14 +48,13 @@
 where
 
 import Control.Parallel.Strategies
-import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.ByteString.Lazy.Char8 as BL
 import Data.List (maximumBy)
 import Data.Ord (comparing)
 import qualified Data.Vector.Unboxed as V
 import qualified ELynx.Data.Alphabet.Alphabet as A
 import ELynx.Data.Alphabet.Character
 import ELynx.Data.Sequence.Defaults
-import ELynx.Tools
 import qualified Text.Printf as P
 import Prelude hiding
   ( concat,
@@ -65,22 +64,22 @@
   ( length,
   )
 
--- | For now, 'Name's are just 'L.ByteString's.
-type Name = L.ByteString
+-- | For now, 'Name's are just 'BL.ByteString's.
+type Name = BL.ByteString
 
 -- | The description of a sequence.
-type Description = L.ByteString
+type Description = BL.ByteString
 
 -- | The vector of characters of a sequence.
 type Characters = V.Vector Character
 
 -- | Convert byte string to sequence characters.
-fromByteString :: L.ByteString -> Characters
-fromByteString = V.fromList . map fromChar . L.unpack
+fromByteString :: BL.ByteString -> Characters
+fromByteString = V.fromList . map fromChar . BL.unpack
 
 -- | Convert sequence characters to byte string.
-toByteString :: Characters -> L.ByteString
-toByteString = L.pack . map toChar . V.toList
+toByteString :: Characters -> BL.ByteString
+toByteString = BL.pack . map toChar . V.toList
 
 -- | Sequences have a name, a possibly empty description, a code and hopefully a
 -- lot of data.
@@ -92,33 +91,51 @@
   }
   deriving (Show, Eq)
 
-getInfo :: Sequence -> L.ByteString
+alignRight :: Int -> BL.ByteString -> BL.ByteString
+alignRight n s =
+  BL.replicate (fromIntegral n - l) ' ' <> BL.take (fromIntegral n) s
+  where
+    l = BL.length s
+
+alignLeft :: Int -> BL.ByteString -> BL.ByteString
+alignLeft n s =
+  BL.take (fromIntegral n) s <> BL.replicate (fromIntegral n - l) ' '
+  where
+    l = BL.length s
+
+getInfo :: Sequence -> BL.ByteString
 getInfo s =
-  L.unwords
+  BL.unwords
     [ alignLeft nameWidth (name s),
-      alignRight fieldWidth (L.pack $ show $ alphabet s),
-      alignRight fieldWidth (L.pack . show $ len),
-      alignRight fieldWidth (L.pack $ P.printf "%2.2f" pGaps)
+      alignRight fieldWidth (BL.pack $ show $ alphabet s),
+      alignRight fieldWidth (BL.pack . show $ len),
+      alignRight fieldWidth (BL.pack $ P.printf "%2.2f" pGaps)
     ]
   where
     len = length s
     nGaps = countGaps s
     pGaps = 100 * fromIntegral nGaps / fromIntegral len :: Double
 
+-- If a string is longer than a given value, trim it and add some dots.
+summarizeByteString :: Int -> BL.ByteString -> BL.ByteString
+summarizeByteString l s
+  | BL.length s >= fromIntegral l = BL.take (fromIntegral l) s <> BL.pack "..."
+  | otherwise = s
+
 -- | Trim and show a 'Sequence'.
-summarize :: Sequence -> L.ByteString
+summarize :: Sequence -> BL.ByteString
 summarize s =
-  L.unwords
+  BL.unwords
     [getInfo s, summarizeByteString summaryLength $ toByteString (characters s)]
 
 -- | Trim and show a list of 'Sequence's.
-summarizeSequences :: [Sequence] -> L.ByteString
+summarizeSequences :: [Sequence] -> BL.ByteString
 summarizeSequences ss = header ss <> body (take summaryNSequences ss)
 
 -- | Header printed before 'Sequence' list.
-tableHeader :: L.ByteString
+tableHeader :: BL.ByteString
 tableHeader =
-  L.unwords
+  BL.unwords
     [ alignLeft nameWidth "Name",
       alignRight fieldWidth "Code",
       alignRight fieldWidth "Length",
@@ -127,15 +144,15 @@
     ]
 
 -- | A short description of the sequence.
-header :: [Sequence] -> L.ByteString
+header :: [Sequence] -> BL.ByteString
 header ss =
-  L.unlines $
+  BL.unlines $
     reportIfSubsetIsShown
-      ++ [ L.pack $
+      ++ [ BL.pack $
              "For each sequence, the "
                ++ show summaryLength
                ++ " first bases are shown.",
-           L.pack $ "List contains " ++ show (Pr.length ss) ++ " sequences.",
+           BL.pack $ "List contains " ++ show (Pr.length ss) ++ " sequences.",
            "",
            tableHeader
          ]
@@ -147,12 +164,12 @@
         ++ show (Pr.length ss)
         ++ " sequences are shown."
     reportIfSubsetIsShown
-      | l > summaryNSequences = [L.pack s]
+      | l > summaryNSequences = [BL.pack s]
       | otherwise = []
 
 -- | Trim and show a list of 'Sequence's.
-body :: [Sequence] -> L.ByteString
-body ss = L.unlines (map summarize ss `using` parListChunk 5 rdeepseq)
+body :: [Sequence] -> BL.ByteString
+body ss = BL.unlines (map summarize ss `using` parListChunk 5 rdeepseq)
 
 -- | Calculate length of 'Sequence'.
 length :: Sequence -> Int
@@ -161,6 +178,9 @@
 -- | Check if all 'Sequence's have equal length.
 equalLength :: [Sequence] -> Bool
 equalLength = allEqual . map length
+  where
+    allEqual [] = True
+    allEqual xs = all (== head xs) $ tail xs
 
 -- | Find the longest 'Sequence' in a list.
 longest :: [Sequence] -> Sequence
@@ -180,16 +200,16 @@
   | i /= j =
     error $
       "concatenate: Sequences do not have equal names: "
-        ++ L.unpack i
+        ++ BL.unpack i
         ++ ", "
-        ++ L.unpack j
+        ++ BL.unpack j
         ++ "."
   | d /= f =
     error $
       "concatenate: Sequences do not have equal descriptions: "
-        ++ L.unpack d
+        ++ BL.unpack d
         ++ ", "
-        ++ L.unpack f
+        ++ BL.unpack f
         ++ "."
   | c /= k =
     error $
diff --git a/src/ELynx/Data/Sequence/Translate.hs b/src/ELynx/Data/Sequence/Translate.hs
--- a/src/ELynx/Data/Sequence/Translate.hs
+++ b/src/ELynx/Data/Sequence/Translate.hs
@@ -19,7 +19,13 @@
 import qualified ELynx.Data.Alphabet.Character as C
 import ELynx.Data.Character.Codon
 import ELynx.Data.Sequence.Sequence
-import ELynx.Tools
+
+-- Chop list into chunks of given length. If the last chop is shorter than
+-- length, it is dropped.
+chopVec :: V.Unbox a => Int -> V.Vector a -> [V.Vector a]
+chopVec n xs
+  | V.length xs < n = []
+  | otherwise = V.take n xs : chopVec n (V.drop n xs)
 
 -- | Translate a sequence from 'DNA' or 'DNAX' to 'ProteinS'.
 translateSeq :: UniversalCode -> Int -> Sequence -> Sequence
diff --git a/src/ELynx/Export/Sequence/Fasta.hs b/src/ELynx/Export/Sequence/Fasta.hs
--- a/src/ELynx/Export/Sequence/Fasta.hs
+++ b/src/ELynx/Export/Sequence/Fasta.hs
@@ -18,19 +18,19 @@
   )
 where
 
-import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.ByteString.Lazy.Char8 as BL
 import ELynx.Data.Sequence.Sequence
 
-fastaHeader :: L.ByteString -> L.ByteString -> L.ByteString
+fastaHeader :: BL.ByteString -> BL.ByteString -> BL.ByteString
 fastaHeader n d =
-  L.singleton '>' <> n <> if L.null d then L.empty else L.pack " " <> d
+  BL.singleton '>' <> n <> if BL.null d then BL.empty else BL.pack " " <> d
 
 -- | Convert a 'Sequence' to Fasta format.
-sequenceToFasta :: Sequence -> L.ByteString
+sequenceToFasta :: Sequence -> BL.ByteString
 sequenceToFasta s =
-  L.unlines [fastaHeader (name s) (description s), toByteString $ characters s]
+  BL.unlines [fastaHeader (name s) (description s), toByteString $ characters s]
 
 -- | Convert a list 'Sequence's to Fasta format. A newline is added between any
 -- two 'Sequence's.
-sequencesToFasta :: [Sequence] -> L.ByteString
-sequencesToFasta ss = L.concat $ map sequenceToFasta ss
+sequencesToFasta :: [Sequence] -> BL.ByteString
+sequencesToFasta ss = BL.concat $ map sequenceToFasta ss
diff --git a/src/ELynx/Import/Sequence/Fasta.hs b/src/ELynx/Import/Sequence/Fasta.hs
--- a/src/ELynx/Import/Sequence/Fasta.hs
+++ b/src/ELynx/Import/Sequence/Fasta.hs
@@ -14,72 +14,57 @@
 -- Parse FASTA files.
 --
 -- [NCBI file specifications](https://blast.ncbi.nlm.nih.gov/Blast.cgi?CMD=Web&PAGE_TYPE=BlastDocs&DOC_TYPE=BlastHelp).
---
--- For more complicated parsers, try to use a [lexer](https://hackage.haskell.org/package/megaparsec-7.0.1/docs/Text-Megaparsec-Byte-Lexer.html).
 module ELynx.Import.Sequence.Fasta
-  ( Parser,
-    fastaSequence,
+  ( fastaSequence,
     fasta,
   )
 where
 
-import Control.Monad
-import qualified Data.ByteString.Lazy.Char8 as L
+import Control.Applicative
+import qualified Data.Attoparsec.ByteString as AS
+import qualified Data.Attoparsec.ByteString.Char8 as AC
+import qualified Data.ByteString.Lazy.Char8 as BL
 import qualified Data.Set as S
-import Data.Void
-import Data.Word8
-  ( Word8,
-    isAlphaNum,
-  )
+import Data.Word8 (Word8)
 import ELynx.Data.Alphabet.Alphabet as A
 import ELynx.Data.Alphabet.Character
 import ELynx.Data.Sequence.Sequence
-import ELynx.Tools
-import Text.Megaparsec
-import Text.Megaparsec.Byte
 
--- | Shortcut.
-type Parser = Parsec Void L.ByteString
-
-isSpecial :: Word8 -> Bool
-isSpecial w = w `elem` map c2w ['_', '|', '.', '-']
-
-isHeader :: Word8 -> Bool
-isHeader w = isAlphaNum w || isSpecial w
+isSpecial :: Char -> Bool
+isSpecial w = w `elem` ['_', '|', '.', '-']
 
-isHorizontalSpace :: Word8 -> Bool
-isHorizontalSpace w = (w == c2w ' ') || (w == c2w '\t')
+isHeader :: Char -> Bool
+isHeader w = AC.isAlpha_ascii w || AC.isDigit w || isSpecial w
 
-sequenceHeader :: Parser (L.ByteString, L.ByteString)
+sequenceHeader :: AS.Parser (BL.ByteString, BL.ByteString)
 sequenceHeader = do
-  _ <- char (c2w '>')
-  n <- takeWhile1P (Just "Name") isHeader
-  _ <- takeWhileP (Just "Horizontal space") isHorizontalSpace
-  d <- takeWhileP (Just "Description") isHeader
-  _ <- eol
-  return (n, d)
+  _ <- AC.char '>'
+  n <- AC.takeWhile1 isHeader
+  _ <- AS.takeWhile AC.isHorizontalSpace
+  d <- AC.takeWhile isHeader
+  _ <- AC.endOfLine
+  return (BL.fromStrict n, BL.fromStrict d)
 
 -- It is a little faster to directly pass the set of allowed characters. Then,
 -- this set only has to be calculcated once per sequence in 'fastaSequence'.
-sequenceLine :: S.Set Word8 -> Parser L.ByteString
+sequenceLine :: S.Set Word8 -> AS.Parser BL.ByteString
 sequenceLine s = do
   -- XXX: Will fail for non-capital letters.
-  !xs <- takeWhile1P (Just "Alphabet character") (`S.member` s)
-  _ <- void eol <|> eof
-  return xs
+  !xs <- AS.takeWhile1 (`S.member` s)
+  return (BL.fromStrict xs)
 
 -- XXX: If sequences are parsed line by line, the lines have to be copied when
 -- forming the complete sequence. This is not memory efficient.
 
 -- | Parse a sequence of characters.
-fastaSequence :: Alphabet -> Parser Sequence
+fastaSequence :: Alphabet -> AS.Parser Sequence
 fastaSequence a = do
   (n, d) <- sequenceHeader
   let !alph = S.map toWord (A.all . alphabetSpec $ a)
-  lns <- some (sequenceLine alph)
-  _ <- many eol
-  return $ Sequence n d a (fromByteString $ L.concat lns)
+  lns <- sequenceLine alph `AS.sepBy1` AC.endOfLine
+  _ <- many AC.endOfLine
+  return $ Sequence n d a (fromByteString $ BL.concat lns)
 
 -- | Parse a Fasta file with given 'Alphabet'.
-fasta :: Alphabet -> Parser [Sequence]
-fasta a = some (fastaSequence a) <* eof
+fasta :: Alphabet -> AS.Parser [Sequence]
+fasta a = some (fastaSequence a) <* AS.endOfInput
diff --git a/test/ELynx/Data/Sequence/AlignmentSpec.hs b/test/ELynx/Data/Sequence/AlignmentSpec.hs
--- a/test/ELynx/Data/Sequence/AlignmentSpec.hs
+++ b/test/ELynx/Data/Sequence/AlignmentSpec.hs
@@ -13,7 +13,7 @@
   )
 where
 
-import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.ByteString.Lazy.Char8 as BL
 import qualified Data.Matrix.Unboxed as M
 import ELynx.Data.Alphabet.Alphabet
 import ELynx.Data.Alphabet.Character
@@ -31,8 +31,8 @@
 ssA :: Alignment
 ssA =
   Alignment
-    (map L.pack ["SEQUENCE_1", "SEQUENCE_2", "SEQUENCE_3"])
-    (replicate 3 L.empty)
+    (map BL.pack ["SEQUENCE_1", "SEQUENCE_2", "SEQUENCE_3"])
+    (replicate 3 BL.empty)
     DNAI
     ssData
 
diff --git a/test/ELynx/Data/Sequence/SequenceSpec.hs b/test/ELynx/Data/Sequence/SequenceSpec.hs
--- a/test/ELynx/Data/Sequence/SequenceSpec.hs
+++ b/test/ELynx/Data/Sequence/SequenceSpec.hs
@@ -13,7 +13,7 @@
   )
 where
 
-import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.ByteString.Lazy.Char8 as BL
 import ELynx.Data.Alphabet.Alphabet
 import ELynx.Data.Sequence.Sequence
 import ELynx.Import.Sequence.Fasta
@@ -26,17 +26,13 @@
 fastaDifferentLengthTrimmedFN :: FilePath
 fastaDifferentLengthTrimmedFN = "data/NucleotideDifferentLengthTrimmed.fasta"
 
-longestSequenceInFileBS :: L.ByteString
+longestSequenceInFileBS :: BL.ByteString
 longestSequenceInFileBS =
-  L.unlines $
-    map L.pack [">SEQUENCE_3", "ATTTAAAAAAACCCAAAACCCGGGCCCCGGGTTTTTTTA"]
+  BL.unlines $
+    map BL.pack [">SEQUENCE_3", "ATTTAAAAAAACCCAAAACCCGGGCCCCGGGTTTTTTTA"]
 
 longestSequenceInFile :: Sequence
-longestSequenceInFile =
-  parseByteStringWith
-    "Fasta byte string"
-    (fastaSequence DNA)
-    longestSequenceInFileBS
+longestSequenceInFile = parseByteStringWith (fastaSequence DNA) longestSequenceInFileBS
 
 spec :: Spec
 spec = do
diff --git a/test/ELynx/Export/Sequence/FastaSpec.hs b/test/ELynx/Export/Sequence/FastaSpec.hs
--- a/test/ELynx/Export/Sequence/FastaSpec.hs
+++ b/test/ELynx/Export/Sequence/FastaSpec.hs
@@ -30,9 +30,5 @@
       $ do
         ss <- parseFileWith (fasta DNAI) fastaNucleotideIUPACFN
         let f = sequencesToFasta ss
-            ss' =
-              parseByteStringWith
-                "Fasta byte string created from sequence object"
-                (fasta DNAI)
-                f
+            ss' = parseByteStringWith (fasta DNAI) f
         ss `shouldBe` ss'
