diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2012, 2013, 2014, Anders Claesson
+Copyright (c) 2012--2015, Anders Claesson
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 ---
-title: SLOANE(1) Sloane User Manual | Version 1.9.3
-date: 24 Sep 2014
+title: SLOANE(1) Sloane User Manual | Version 2.0.0
+date: 2 Jan 2015
 ---
 
 # NAME
@@ -10,103 +10,98 @@
 
 # SYNOPSIS
 
-sloane [lookup | grep] 
-  [-a | --all | -k *keys* | --url] [-n *entries*] *terms* ...  
-sloane filter [--invert]  
-sloane update  
-sloane version  
+sloane [-a|--all] [-k KEYS] [-n N] [--url] [--local] [TERMS...]
 
-# DESCRIPTION
+sloane [--filter] [--invert]
 
-The `sloane lookup` command searches Sloane's On-Line Encyclopedia of
-Integer Sequences (OEIS). The search terms are typically the leading
-term of a sequence. For example,
+sloane [--transform NAME] [--list-transforms]
 
-    sloane lookup 1,1,2,5,15,52,203,877,4140
+sloane [--update] [--version] [--help]
 
-returns entry A000110 (Bell numbers), and four more entries. If no
-command is given sloane will fall back to the lookup command, so the
-above query can more simply be given as
+# DESCRIPTION
 
+The `sloane` command searches Sloane's On-Line Encyclopedia of Integer
+Sequences (OEIS). The search terms are typically the leading term of a
+sequence. For example,
+
     sloane 1,1,2,5,15,52,203,877,4140
 
-One can also search by sequence id (A-number), or even search for
-arbitrary words. See the **EXAMPLES** section.
+returns entry A000110 (Bell numbers), and four more entries.  One can
+also search by sequence id (A-number), or even search for arbitrary
+words. See the **EXAMPLES** section.
 
-Alternatively, using the `sloane grep` command, the search can be done
-locally against a downloaded list of known sequences. This mode works by
-"grepping" for the query in the sequence field.
+Alternatively, using the `--local` option, the search can be done
+locally against a downloaded local database of known sequences. This
+mode works by "grepping" for the query in the sequence field.
 
-To check a large number of sequences one can use the `sloane filter`
-command.  It reads the standard input line-by-line, if the sequence read
-is in the local database, then it is returned to the standard output; if
-not, it is ignored. This way onw can quickly filter out the sequences
-from the input that are in the local database. In other words, assuming
-that *FILE* contains one sequence per line,
+To check a large number of sequences one can use `--filter`.  When this
+option is set, `sloane` reads the standard input line-by-line, if the
+sequence read is in the local database, then it is returned to the
+standard output; if not, it is ignored. This way one can quickly filter
+out the sequences from the input that are in the local database. In
+other words, assuming that *FILE* contains one sequence per line,
 
-    sloane filter <FILE
+    sloane --filter <FILE
 
 returns the subset of the sequences in *FILE* that are in the local
 database. To also look-up the names of those sequences one could, for
 instance, run
 
-    sloane filter <FILE | xargs -L1 --verbose sloane grep
+    sloane --filter <FILE | xargs -L1 --verbose sloane --local
 
 Sloane normally crops long lines to fit the widths of the terminal. If
 this is unwanted, pipe the output through cat or less:
 
-    sloane lookup -a id:A000110 | less -R
+    sloane -a id:A000110 | less -R
 
 # OPTIONS
 
---help
-:   Display a short help message
-
-# COMMANDS
-
-## `lookup`
-
-Lookup a sequence, or other search term, in OEIS
-
 -a, --all
 :   Print all fields
 
--k *keys*
+-k *KEYS*
 :   Keys of fields to print (default: SN)
 
---url
-:   Print URLs of found entries (but nothing else)
-
--n *entries*
+-n *N*
 :   Fetch at most this many entries (default: 5)
 
-
-## `grep`
-
-Grep for a sequence in the local database. Same options as for the
-`lookup` command apply.
+--url
+:   Print URLs of found entries (but nothing else)
 
-## `filter`
+--local
+:   Grep for a sequence in the local database.
 
-Read sequences from stdin and return those that are in the local
-database.
+--filter
+:   Read sequences from stdin and return those that are in the local
+    database.
 
 --invert
-:   Return sequences *not* in the database.
+:   Return sequences *not* in the database. This option has no effect
+    unless `--filter` is also set.
 
-## `update`
+--transform *NAME*
+:   Apply the named transform to the input sequence. If the resulting
+    sequence is integral print it to stdout; else print nothing.
+    Most of the transforms and their names are taken from
+    <https://oeis.org/transforms.txt>.
 
-Update the local database.
+--list-transforms
+:   List the names of all transforms.
 
-## `version`
+--update
+:   Update the local database.
 
-Print version information.
+--version
+:   Print version information.
 
+--help
+:   Briefly describe the available options.
+
 # EXAMPLES
 
 The most common search is for entries matching a sequence of consecutive terms:
 
-    sloane lookup 1,3,19,183,2371,38703
+    sloane 1,3,19,183,2371,38703
 
 At the time of writing this particular query would return
 
@@ -118,21 +113,21 @@
 option. For instance, the following search shows the sequence, name,
 comments, and formula fields of the sequence whose A-number is A006531:
 
-    sloane lookup -k SNCF id:A006531
+    sloane -k SNCF id:A006531
 
 The next example returns at most 3 results of a free text search:
 
-    sloane lookup -n 3 "(2+2)-free posets"
+    sloane -n 3 "(2+2)-free posets"
 
 To view the full entries of these 3 results in a browser (e.g., Firefox)
 one can use the url option:
 
-    firefox `sloane lookup --url -n 3 "(2+2)-free posets"`
+    firefox `sloane --url -n 3 "(2+2)-free posets"`
 
 In the final example the local cache is used to filter out sequences
 from the standard input that are in OEIS:
 
-    sloane filter <<END
+    sloane --filter <<END
     1,2,3,6,11,23,47,106,235           # Comma separated integers
     1 2 444 90 120                     # Space separated integers
     '(3 9 27 88 123)                   # S-expression
diff --git a/Sloane/Config.hs b/Sloane/Config.hs
--- a/Sloane/Config.hs
+++ b/Sloane/Config.hs
@@ -12,7 +12,7 @@
 type URL = String
 
 data Config = Config
-    { name      :: String
+    { nameVer   :: String
     , home      :: FilePath
     , sloaneDir :: FilePath
     , sloaneDB  :: FilePath
@@ -29,7 +29,7 @@
     h <- getHomeDirectory
     let dsloane = h </> ".sloane"
     return Config
-        { name      = "sloane 1.9.3"
+        { nameVer   = "sloane 2.0.0"
         , home      = h
         , sloaneDir = dsloane
         , sloaneDB  = dsloane </> "sloane.db"
diff --git a/Sloane/DB.hs b/Sloane/DB.hs
--- a/Sloane/DB.hs
+++ b/Sloane/DB.hs
@@ -7,10 +7,10 @@
 module Sloane.DB
     ( DB
     , Reply
-    , initDB
-    , readDB
-    , writeDB
-    , putDB
+    , update
+    , read
+    , write
+    , put
     , null
     , insert
     , lookup
@@ -20,21 +20,22 @@
     , parseOEISEntries
     ) where
 
-import           Prelude                    hiding (lookup, null, take)
+import           Prelude                    hiding (lookup, null, take, read)
 import qualified Prelude                    as P
 import           Data.List                  (intersect)
 import           Data.ByteString            (ByteString)
+import qualified Data.ByteString            as B
 import qualified Data.ByteString.Lazy       as BL
-import           Data.Map                   (Map, (!))
-import qualified Data.Map                   as M
-import           Data.Serialize
+import           Data.Map.Strict            (Map, (!))
+import qualified Data.Map.Strict            as M
+import           Data.Serialize             hiding (put)
 import           Data.Text                  (Text)
 import qualified Data.Text                  as T
 import qualified Data.Text.IO               as IO
 import           Data.Text.Encoding         (decodeUtf8, encodeUtf8)
 import           Control.Monad              (forM_, unless)
-import qualified Codec.Compression.GZip     as GZip
-import           Network.Curl.Download.Lazy (openLazyURI)
+import qualified Codec.Compression.GZip     as GZ
+import           Network.Curl.Download      (openURI)
 import           System.Console.ANSI
 import           Sloane.Config
 import           System.Directory
@@ -47,50 +48,50 @@
 type DB      = Map ANumber Reply
 type DBRaw   = Map ByteString (Map Char ByteString)
 
-encodeDB :: DB -> DBRaw
-encodeDB = M.mapKeys encodeUtf8 . M.map (M.map encodeUtf8)
+encodeUtf8' :: DB -> DBRaw
+encodeUtf8'= M.mapKeys encodeUtf8 . M.map (M.map encodeUtf8)
 
-decodeDB :: DBRaw -> DB
-decodeDB = M.mapKeys decodeUtf8 . M.map (M.map decodeUtf8)
+decodeUtf8' :: DBRaw -> DB
+decodeUtf8' = M.mapKeys decodeUtf8 . M.map (M.map decodeUtf8)
 
-compress :: ByteString -> BL.ByteString
-compress = GZip.compress . BL.fromStrict
+gzCompress :: ByteString -> ByteString
+gzCompress = BL.toStrict . GZ.compress . BL.fromStrict
 
-compressDB :: DB -> BL.ByteString
-compressDB = compress . encode . encodeDB
+compressDB :: DB -> ByteString
+compressDB = gzCompress . encode . encodeUtf8'
 
-decompress :: BL.ByteString -> ByteString
-decompress = BL.toStrict . GZip.decompress
+gzDecompress :: ByteString -> ByteString
+gzDecompress = BL.toStrict . GZ.decompress . BL.fromStrict
 
-decompressDB :: BL.ByteString -> Either String DB
-decompressDB = fmap decodeDB . decode . decompress
+decompressDB :: ByteString -> Either String DB
+decompressDB = fmap decodeUtf8' . decode . gzDecompress
 
-initDB :: Config -> IO ()
-initDB cfg = do
+update :: Config -> IO ()
+update cfg = do
     createDirectoryIfMissing False (sloaneDir cfg)
     putStrLn $ "Downloading " ++ sURL cfg
-    dbS <- openLazyURI (sURL cfg) >>= either error (return . mkDB 'S')
+    dbS <- openURI (sURL cfg) >>= either error (return . mkDB 'S')
     putStrLn $ "Downloading " ++ nURL cfg
-    dbN <- openLazyURI (nURL cfg) >>= either error (return . mkDB 'N')
+    dbN <- openURI (nURL cfg) >>= either error (return . mkDB 'N')
     putStrLn "Building database"
-    writeDB cfg $ unionDB dbS dbN
+    write cfg $ unionDB dbS dbN
     putStrLn "Done."
   where
     unionDB = M.unionWith M.union
-    mkDB key = mkMap key . decodeUtf8 . decompress
+    mkDB key = mkMap key . decodeUtf8 . gzDecompress
     mkMap key = M.fromList . map (aNumberAndReply key) . drop 4 . T.lines
     mkReply key = M.singleton key . T.dropWhile (==',') . T.drop 8
     aNumberAndReply key line = (T.take 7 line, mkReply key line)
 
-readDB :: Config -> IO DB
-readDB cfg = doesFileExist (sloaneDB cfg) >>= \updated ->
+read :: Config -> IO DB
+read cfg = doesFileExist (sloaneDB cfg) >>= \updated ->
     if updated
-        then BL.readFile (sloaneDB cfg) >>= either error return . decompressDB
+        then B.readFile (sloaneDB cfg) >>= either error return . decompressDB
         else error $ "No local database found. " ++
-                     "You need to run \"sloane update\" first."
+                     "You need to run \"sloane --update\" first."
 
-writeDB :: Config -> DB -> IO ()
-writeDB cfg = BL.writeFile (sloaneDB cfg) . compressDB
+write :: Config -> DB -> IO ()
+write cfg = B.writeFile (sloaneDB cfg) . compressDB
 
 null :: DB -> Bool
 null = M.null
@@ -102,7 +103,10 @@
 lookup = M.lookup
 
 grep :: Text -> DB -> DB
-grep q = M.filter $ \reply -> q `T.isInfixOf` (reply ! 'S')
+grep q = M.filter $ \reply ->
+             case M.lookup 'S' reply of
+                 Nothing -> False
+                 Just r  -> q `T.isInfixOf` r
 
 take :: Int -> DB -> DB
 take n = M.fromList . P.take n . M.toList
@@ -125,8 +129,8 @@
     parseWords (key:aNum:rest) = singleton aNum (T.head key) (T.unwords rest)
     parseWords _ = M.empty
 
-putDB :: Config -> [Key] -> DB -> IO ()
-putDB cfg keys db = do
+put :: Config -> [Key] -> DB -> IO ()
+put cfg keys db = do
     unless (null db) $ putStrLn ""
     forM_ (M.toList db) $ \(aNum, reply) -> do
         forM_ (keys `intersect` M.keys reply) $ \key -> do
diff --git a/Sloane/GF.hs b/Sloane/GF.hs
new file mode 100644
--- /dev/null
+++ b/Sloane/GF.hs
@@ -0,0 +1,162 @@
+-- |
+-- Copyright   : Anders Claesson 2012-2015
+-- Maintainer  : Anders Claesson <anders.claesson@gmail.com>
+-- License     : BSD-3
+--
+module Sloane.GF
+    ( Series (..)
+    , GF
+    , imap
+    , diff
+    , squareRoot
+    , (^^^)
+    , o
+    , ogf
+    , egf
+    , ogfCoeffs
+    , egfCoeffs
+    )where
+
+import Data.List
+import Data.Maybe
+import Data.Ratio
+
+-- Reference: M. D. McIlroy, The music of streams,
+-- Information Processing Letters 77 (2001) 189-195.
+
+newtype Series a = Series { coeffs :: [a] } deriving (Show, Eq)
+
+type GF = Series Rational
+
+instance Functor Series where
+    fmap f = Series . fmap f . coeffs
+
+instance (Eq a, Num a) => Num (Series a) where
+    (+) = lift2 add
+    (*) = lift2 mul
+    fromInteger c = Series [fromInteger c]
+    negate = fmap negate
+    signum = undefined
+    abs    = undefined
+
+instance (Eq a, Fractional a) => Fractional (Series a) where
+     fromRational c = Series [fromRational c]
+     (/) = lift2 divide
+
+degree :: (Num a, Eq a) => Series a -> Int
+degree (Series [0]) = -1
+degree (Series cs)  = length cs - 1
+-- degree (Series cs)  = length (dropWhile (==0) (reverse cs)) - 1
+
+o :: (Eq a, Fractional a) => Series a -> Series a -> Series a
+o = lift2 compose
+
+lift :: ([a] -> [b]) -> Series a -> Series b
+lift f (Series as) = Series (f as)
+
+lift2 :: ([a] -> [b] -> [c]) -> Series a -> Series b -> Series c
+lift2 op (Series as) (Series bs) = Series (as `op` bs)
+
+add :: Num a => [a] -> [a] -> [a]
+add []     ds     = ds
+add cs     []     = cs
+add (c:cs) (d:ds) = c+d : add cs ds
+
+sub :: Num a => [a] -> [a] -> [a]
+cs `sub` ds = cs `add` map negate ds
+
+(!*!) :: (Eq a, Num a) => a -> a -> a
+(!*!) _ 0 = 0
+(!*!) 0 _ = 0
+(!*!) a b = a*b
+
+mul :: (Eq a, Num a) => [a] -> [a] -> [a]
+mul (c:ct) ds@(d:dt) = c!*!d : map (c !*!) dt `add` (ct `mul` ds)
+mul _      _         = []
+
+divide :: (Eq a, Fractional a) => [a] -> [a] -> [a]
+divide []     (0:_)     = undefined
+divide []      _        = []
+divide (0:ct) (0:dt)    = ct `divide` dt
+divide (c:ct) ds@(d:dt) = q : (ct `sub` ([q] `mul` dt)) `divide` ds where q = c/d
+divide _      []        = undefined
+
+diff :: GF -> GF
+diff = lift dF where {dF [] = []; dF (_:ct) = zipWith (*) [1..] ct}
+
+nthRootApprox :: Integer -> GF -> [GF]
+nthRootApprox n f@(Series (1:_)) =
+    iterateUntilFixed (nthRootNext n f) (Series [1])
+
+nthRootNext :: Integer -> GF -> GF -> GF
+nthRootNext n f g = Series (take (1 + 2*degree f) ds)
+  where
+    Series ds = (Series [n%1-1] * g^n + f) / (Series [n%1] * g^(n-1))
+
+iterateUntilFixed :: Eq a => (a -> a) -> a -> [a]
+iterateUntilFixed f x = x : (if x == y then [] else ys)
+  where
+    ys@(y:_) = iterateUntilFixed f (f x)
+
+saddlePoint :: Eq a => [a] -> Maybe a
+saddlePoint [c] = Just c
+saddlePoint cs  = listToMaybe [ c | (c,d) <- zip cs (drop 1 cs), c == d ]
+
+nthRoot1 :: Integer -> GF -> GF
+nthRoot1 n f@(Series (1:_)) =
+    let css = transpose (map coeffs (nthRootApprox n f))
+    in Series $ map fromJust (takeWhile isJust (map saddlePoint css))
+
+nthRoot :: Integer -> GF -> GF
+nthRoot _ (Series [])       = Series []
+nthRoot n (Series cs@(c:_)) = Series [d] * nthRoot1 n (Series $ map (/c) cs)
+    where
+      d = toRational (fromRational c ** fromRational (1%n) :: Double)
+
+(^^^) :: GF -> Rational -> GF
+(^^^) f r = case (numerator r, denominator r) of
+              (n, 1) -> f ^^ n
+              (0, _) -> ogf [1]
+              (n, k) -> nthRoot k f ^^ n
+
+-- XXX: Seems correct, but isn't producing anything
+-- nthRoot :: Integer -> GF -> GF
+-- nthRoot n f = sum [ (f-1)^k * fromRational (bin n k) | k<-[0..]]
+--   where
+--     -- bin n k = {1/n choose k}
+--     bin n k = product [(1-i*n+n) % i | i<-[1..k] ] / (n%1)^k
+
+squareRoot :: GF -> GF
+squareRoot = Series . map toRational . squareRoot' . map fromRational . coeffs
+
+squareRoot' :: [Double] -> [Double]
+squareRoot' []     = []
+squareRoot' (c:ct) = ds
+  where
+    ds = d : ct `divide` ([d] `add` ds)
+    d  = sqrt c
+
+compose :: (Eq a, Fractional a) => [a] -> [a] -> [a]
+[]     `compose` _         = []
+(c:_)  `compose` []        = [c]
+(c:ct) `compose` ds@(0:dt) = c : dt `mul` (ct `compose` ds)
+(c:ct) `compose` ds        = [c] `add` ds `mul` (ct `compose` ds)
+                                              -- ct must be finite
+
+imap :: (Integer -> a -> b) -> Series a -> Series b
+imap f = Series . zipWith f [0..] . coeffs
+
+factorial :: Integer -> Integer
+factorial n = product [1..n]
+
+ogf :: Real a => [a] -> GF
+ogf = Series . map toRational
+
+ogfCoeffs :: GF -> [Rational]
+ogfCoeffs = coeffs
+
+egf :: Real a => [a] -> GF
+egf = Series . zipWith (\n c -> toRational c * (1 % factorial n)) [0..]
+
+egfCoeffs :: GF -> [Rational]
+egfCoeffs = ogfCoeffs . imap (\i -> (* (factorial i % 1)))
diff --git a/Sloane/Transform.hs b/Sloane/Transform.hs
new file mode 100644
--- /dev/null
+++ b/Sloane/Transform.hs
@@ -0,0 +1,262 @@
+-- |
+-- Copyright   : Anders Claesson 2012-2015
+-- Maintainer  : Anders Claesson <anders.claesson@gmail.com>
+-- License     : BSD-3
+--
+module Sloane.Transform
+    ( NamedTransform (..)
+    , ($$)
+    , tLEFT
+    , tRIGHT
+    , tM2
+    , tM2i
+    , tBINOMIAL
+    , tBINOMIALi
+    , tBIN1
+    , tBISECT0
+    , tBISECT1
+    , tCONV
+    , tCONVi
+    , tEXPCONV
+    , tDIFF
+    , tEULER
+    , tEXP
+    , tLOG
+    , tNEGATE
+    , tPRODS
+    , tPSUM
+    , tPSUMSIGN
+    , tSTIRLING
+    , tTRISECT0
+    , tTRISECT1
+    , tTRISECT2
+    , tPOINT
+    , tWEIGHT
+    , tPARTITION
+    , transforms
+    , lookupTranform
+    , applyAllTransforms
+    ) where
+
+import Data.List
+import Data.Ratio
+import Data.Monoid
+import Control.Monad
+import Sloane.GF
+
+type Transform = [Rational] -> [Rational]
+
+data NamedTransform = NT
+    { name :: String
+    , eval :: Transform
+    }
+
+instance Show NamedTransform where
+    show = ('t':) . name
+
+instance Eq NamedTransform where
+    t == s = name t == name s
+
+instance Monoid NamedTransform where
+    mempty = NT "" id
+    mappend f g = NT tname teval
+      where
+        tname = name f ++ "." ++ name g
+        teval = eval f     .     eval g
+
+infixr 0 $$
+
+isInteger :: Rational -> Bool
+isInteger = (1==) . denominator
+
+toIntSeq :: [Rational] -> [Integer]
+toIntSeq cs = [ numerator c | c <- cs, all isInteger cs ]
+
+($$) :: NamedTransform -> [Integer] -> [Integer]
+f $$ cs = toIntSeq $ take (length cs) (eval f (map toRational cs))
+
+x :: GF
+x = ogf [0::Integer, 1]
+
+-- addSeq :: [Integer] -> Transform
+-- addSeq seq0 = zipWith (+) seq0
+
+-- mulOGF :: [Integer] -> Transform
+-- mulOGF seq0 = \cs -> ogfCoeffs (ogf seq0 * ogf cs)
+
+-- mulEGF :: [Integer] -> Transform
+-- mulEGF seq0 = \cs -> egfCoeffs (egf seq0 * egf cs)
+
+geoSeries :: Rational -> GF
+geoSeries c = ogf [ c^k | k<-[0::Int ..] ]
+
+expSeries :: Rational -> GF
+expSeries c = egf [ c^k | k<-[0::Int ..] ]
+
+bisect0 :: [a] -> [a]
+bisect0 [] = []
+bisect0 (c:cs) = c : bisect1 cs
+
+bisect1 :: [a] -> [a]
+bisect1 [] = []
+bisect1 (_:cs) = bisect0 cs
+
+trisect0 :: [a] -> [a]
+trisect0 [] = []
+trisect0 (c:cs) = c : trisect2 cs
+
+trisect1 :: [a] -> [a]
+trisect1 [] = []
+trisect1 (_:cs) = trisect0 cs
+
+trisect2 :: [a] -> [a]
+trisect2 [] = []
+trisect2 (_:cs) = trisect1 cs
+
+signed :: GF -> GF
+signed = imap $ \i c -> (-1%1)^i * c
+
+tLEFT :: NamedTransform
+tLEFT = NT "LEFT" (drop 1)
+
+tRIGHT :: NamedTransform
+tRIGHT = NT "RIGHT" (1:)
+
+tM2 :: NamedTransform
+tM2 = NT "M2" f where f [] = []; f (c:cs) = c : map ((2%1)*) cs
+
+tM2i :: NamedTransform
+tM2i = NT "M2i" (\cs -> ogfCoeffs (Series (f cs)))
+  where
+    f [] = []
+    f cs = let (d:ds) = map toRational cs in d : map (/(2%1)) ds
+
+tBINOMIAL :: NamedTransform
+tBINOMIAL = NT "BINOMIAL" (\cs -> egfCoeffs (expSeries (1%1) * egf cs))
+
+tBINOMIALi :: NamedTransform
+tBINOMIALi = NT "BINOMIALi" (\cs -> egfCoeffs (expSeries ((-1)%1) * egf cs))
+
+tBIN1 :: NamedTransform
+tBIN1 = NT "BIN1" (\cs ->
+    drop 1 . egfCoeffs $ -expSeries (-1%1) * signed (egf (0:cs)))
+
+tBISECT0 :: NamedTransform
+tBISECT0 = NT "BISECT0" bisect0
+
+tBISECT1 :: NamedTransform
+tBISECT1 = NT "BISECT1" bisect1
+
+tCONV :: NamedTransform
+tCONV = NT "CONV" (\cs -> ogfCoeffs (ogf cs ^ (2::Int)))
+
+tCONVi :: NamedTransform
+tCONVi = NT "CONVi" (\cs -> ogfCoeffs (squareRoot (ogf cs)))
+
+tEXPCONV :: NamedTransform
+tEXPCONV = NT "EXPCONV" (\cs -> egfCoeffs (egf cs ^ (2::Int)))
+
+tDIFF :: NamedTransform
+tDIFF = NT "DIFF" (\cs -> zipWith (-) (drop 1 cs) cs)
+
+tEULER :: NamedTransform
+tEULER = NT "EULER" (\cs ->
+    let f = product $ zipWith (\n c -> (1 - x^n)^^^c) [1::Int ..] cs
+    in drop 1 $ ogfCoeffs (1/f))
+
+tEULERi :: NamedTransform
+tEULERi = NT "EULERi" undefined
+
+-- EXP converts [a_1, a_2, ...] to [b_1, b_2,...] where
+-- 1 + EGF_B (x) = exp EGF_A (x)
+tEXP :: NamedTransform
+tEXP = NT "EXP" (\cs -> drop 1 . egfCoeffs $ expSeries 1 `o` egf (0:cs))
+
+-- LOG converts [a_1, a_2, ...] to [b_1, b_2,...] where
+-- 1 + EGF_A (x) = exp EGF_B (x) i.e. EGF_B (x) = log(1 + EGF_A (x)).
+tLOG :: NamedTransform
+tLOG = NT "LOG" (\cs -> drop 1 $ egfCoeffs (log1 `o` (-1 * egf (0:cs))))
+  where
+    log1 = Series (0 : [-1 % n | n <- [1..]])
+
+tNEGATE :: NamedTransform
+tNEGATE = NT "NEGATE" f where f [] = []; f (c:cs) = c : map negate cs
+
+tPRODS :: NamedTransform
+tPRODS = NT "PRODS" (drop 1 . scanl (*) (1%1))
+
+tPSUM :: NamedTransform
+tPSUM = NT "PSUM" (drop 1 . scanl (+) (0%1))
+
+tPSUMSIGN :: NamedTransform
+tPSUMSIGN = NT "PSUMSIGN" (ogfCoeffs . (geoSeries (-1%1) *) . ogf)
+
+tSTIRLING :: NamedTransform
+tSTIRLING = NT "STIRLING" (\cs ->
+    drop 1 . egfCoeffs $ egf (0:cs) `o` (expSeries (1%1) - 1))
+
+tTRISECT0 :: NamedTransform
+tTRISECT0 = NT "TRISECT0" trisect0
+
+tTRISECT1 :: NamedTransform
+tTRISECT1 = NT "TRISECT1" trisect1
+
+tTRISECT2 :: NamedTransform
+tTRISECT2 = NT "TRISECT2" trisect2
+
+tPOINT :: NamedTransform
+tPOINT = NT "POINT" (zipWith (*) [0..])
+
+tWEIGHT :: NamedTransform
+tWEIGHT = NT "WEIGHT" $
+    drop 1 . ogfCoeffs . product . zipWith (\n c -> (1 + x^n)^^^c) [1::Int ..]
+
+increasing :: Ord a => [a] -> Bool
+increasing cs = and $ zipWith (<=) cs (drop 1 cs)
+
+tPARTITION :: NamedTransform
+tPARTITION = NT "PARTITION" $ \cs -> do
+    guard $ not (null cs) && all (>0) cs && increasing cs
+    let f = product $ map (\c -> (1 - x^^^c)) (nub cs)
+    drop 1 . ogfCoeffs $ 1/f
+
+-- New transform: tSTIELTJES -- continued fraction coefficients
+
+
+transforms :: [NamedTransform]
+transforms =
+    [ tLEFT
+    , tRIGHT
+    , tM2
+    , tM2i
+    , tBINOMIAL
+    , tBINOMIALi
+    , tBIN1
+    , tBISECT0
+    , tBISECT1
+    , tCONV
+    , tCONVi
+    , tEXPCONV
+    , tDIFF
+    , tEULER
+--    , tEULERi
+    , tEXP
+    , tLOG
+    , tNEGATE
+    , tPRODS
+    , tPSUM
+    , tPSUMSIGN
+    , tSTIRLING
+    , tTRISECT0
+    , tTRISECT1
+    , tTRISECT2
+    , tPOINT
+    , tWEIGHT
+    , tPARTITION
+    ]
+
+lookupTranform :: String -> Maybe NamedTransform
+lookupTranform tname = lookup tname [ (name f, f) | f <- transforms ]
+
+applyAllTransforms :: [Rational] -> [(NamedTransform, [Rational])]
+applyAllTransforms cs = [ (f, eval f cs) | f <- transforms ]
diff --git a/sloane.1 b/sloane.1
--- a/sloane.1
+++ b/sloane.1
@@ -1,68 +1,51 @@
-.TH "SLOANE" "1" "24 Sep 2014" "Sloane User Manual" "Version 1.9.3"
+.TH "SLOANE" "1" "2 Jan 2015" "Sloane User Manual" "Version 2.0.0"
 .SH NAME
 .PP
 sloane \- a command line interface to Sloane\[aq]s On\-Line Encyclopedia
 of Integer Sequences <http://oeis.org>
 .SH SYNOPSIS
 .PP
-sloane [lookup | grep] [\-a | \-\-all | \-k \f[I]keys\f[] | \-\-url]
-[\-n \f[I]entries\f[]] \f[I]terms\f[] ...
-.PD 0
-.P
-.PD
-sloane filter [\-\-invert]
-.PD 0
-.P
-.PD
-sloane update
-.PD 0
-.P
-.PD
-sloane version
+sloane [\-a|\-\-all] [\-k KEYS] [\-n N] [\-\-url] [\-\-local] [TERMS...]
+.PP
+sloane [\-\-filter] [\-\-invert]
+.PP
+sloane [\-\-transform NAME] [\-\-list\-transforms]
+.PP
+sloane [\-\-update] [\-\-version] [\-\-help]
 .SH DESCRIPTION
 .PP
-The \f[C]sloane\ lookup\f[] command searches Sloane\[aq]s On\-Line
-Encyclopedia of Integer Sequences (OEIS).
+The \f[C]sloane\f[] command searches Sloane\[aq]s On\-Line Encyclopedia
+of Integer Sequences (OEIS).
 The search terms are typically the leading term of a sequence.
 For example,
 .IP
 .nf
 \f[C]
-sloane\ lookup\ 1,1,2,5,15,52,203,877,4140
-\f[]
-.fi
-.PP
-returns entry A000110 (Bell numbers), and four more entries.
-If no command is given sloane will fall back to the lookup command, so
-the above query can more simply be given as
-.IP
-.nf
-\f[C]
 sloane\ 1,1,2,5,15,52,203,877,4140
 \f[]
 .fi
 .PP
+returns entry A000110 (Bell numbers), and four more entries.
 One can also search by sequence id (A\-number), or even search for
 arbitrary words.
 See the \f[B]EXAMPLES\f[] section.
 .PP
-Alternatively, using the \f[C]sloane\ grep\f[] command, the search can
-be done locally against a downloaded list of known sequences.
+Alternatively, using the \f[C]\-\-local\f[] option, the search can be
+done locally against a downloaded local database of known sequences.
 This mode works by "grepping" for the query in the sequence field.
 .PP
-To check a large number of sequences one can use the
-\f[C]sloane\ filter\f[] command.
-It reads the standard input line\-by\-line, if the sequence read is in
-the local database, then it is returned to the standard output; if not,
-it is ignored.
-This way onw can quickly filter out the sequences from the input that
+To check a large number of sequences one can use \f[C]\-\-filter\f[].
+When this option is set, \f[C]sloane\f[] reads the standard input
+line\-by\-line, if the sequence read is in the local database, then it
+is returned to the standard output; if not, it is ignored.
+This way one can quickly filter out the sequences from the input that
 are in the local database.
 In other words, assuming that \f[I]FILE\f[] contains one sequence per
 line,
 .IP
 .nf
 \f[C]
-sloane\ filter\ <FILE
+sloane\ \-\-filter\ <FILE
 \f[]
 .fi
 .PP
@@ -73,7 +56,7 @@
 .IP
 .nf
 \f[C]
-sloane\ filter\ <FILE\ |\ xargs\ \-L1\ \-\-verbose\ sloane\ grep
+sloane\ \-\-filter\ <FILE\ |\ xargs\ \-L1\ \-\-verbose\ sloane\ \-\-local
 \f[]
 .fi
 .PP
@@ -82,58 +65,76 @@
 .IP
 .nf
 \f[C]
-sloane\ lookup\ \-a\ id:A000110\ |\ less\ \-R
+sloane\ \-a\ id:A000110\ |\ less\ \-R
 \f[]
 .fi
 .SH OPTIONS
 .TP
-.B \-\-help
-Display a short help message
-.RS
-.RE
-.SH COMMANDS
-.SS \f[C]lookup\f[]
-.PP
-Lookup a sequence, or other search term, in OEIS
-.TP
 .B \-a, \-\-all
 Print all fields
 .RS
 .RE
 .TP
-.B \-k \f[I]keys\f[]
+.B \-k \f[I]KEYS\f[]
 Keys of fields to print (default: SN)
 .RS
 .RE
 .TP
+.B \-n \f[I]N\f[]
+Fetch at most this many entries (default: 5)
+.RS
+.RE
+.TP
 .B \-\-url
 Print URLs of found entries (but nothing else)
 .RS
 .RE
 .TP
-.B \-n \f[I]entries\f[]
-Fetch at most this many entries (default: 5)
+.B \-\-local
+Grep for a sequence in the local database.
 .RS
 .RE
-.SS \f[C]grep\f[]
-.PP
-Grep for a sequence in the local database.
-Same options as for the \f[C]lookup\f[] command apply.
-.SS \f[C]filter\f[]
-.PP
+.TP
+.B \-\-filter
 Read sequences from stdin and return those that are in the local
 database.
+.RS
+.RE
 .TP
 .B \-\-invert
 Return sequences \f[I]not\f[] in the database.
+This option has no effect unless \f[C]\-\-filter\f[] is also set.
 .RS
 .RE
-.SS \f[C]update\f[]
-.PP
+.TP
+.B \-\-transform \f[I]NAME\f[]
+Apply the named transform to the input sequence.
+If the resulting sequence is integral print it to stdout; else print
+nothing.
+Most of the transforms and their names are taken from
+<https://oeis.org/transforms.txt>.
+.RS
+.RE
+.TP
+.B \-\-list\-transforms
+List the names of all transforms.
+.RS
+.RE
+.TP
+.B \-\-update
 Update the local database.
-.SS \f[C]version\f[]
-.PP
+.RS
+.RE
+.TP
+.B \-\-version
 Print version information.
+.RS
+.RE
+.TP
+.B \-\-help
+Briefly describe the available options.
+.RS
+.RE
 .SH EXAMPLES
 .PP
 The most common search is for entries matching a sequence of consecutive
@@ -141,7 +142,7 @@
 .IP
 .nf
 \f[C]
-sloane\ lookup\ 1,3,19,183,2371,38703
+sloane\ 1,3,19,183,2371,38703
 \f[]
 .fi
 .PP
@@ -162,7 +163,7 @@
 .IP
 .nf
 \f[C]
-sloane\ lookup\ \-k\ SNCF\ id:A006531
+sloane\ \-k\ SNCF\ id:A006531
 \f[]
 .fi
 .PP
@@ -170,7 +171,7 @@
 .IP
 .nf
 \f[C]
-sloane\ lookup\ \-n\ 3\ "(2+2)\-free\ posets"
+sloane\ \-n\ 3\ "(2+2)\-free\ posets"
 \f[]
 .fi
 .PP
@@ -179,7 +180,7 @@
 .IP
 .nf
 \f[C]
-firefox\ `sloane\ lookup\ \-\-url\ \-n\ 3\ "(2+2)\-free\ posets"`
+firefox\ `sloane\ \-\-url\ \-n\ 3\ "(2+2)\-free\ posets"`
 \f[]
 .fi
 .PP
@@ -188,7 +189,7 @@
 .IP
 .nf
 \f[C]
-sloane\ filter\ <<END
+sloane\ \-\-filter\ <<END
 1,2,3,6,11,23,47,106,235\ \ \ \ \ \ \ \ \ \ \ #\ Comma\ separated\ integers
 1\ 2\ 444\ 90\ 120\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ Space\ separated\ integers
 \[aq](3\ 9\ 27\ 88\ 123)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ S\-expression
diff --git a/sloane.cabal b/sloane.cabal
--- a/sloane.cabal
+++ b/sloane.cabal
@@ -1,5 +1,5 @@
 Name:                sloane
-Version:             1.9.3
+Version:             2.0.0
 Synopsis:            A command line interface to Sloane's On-Line Encyclopedia
                      of Integer Sequences
 Description:         A command line interface to Sloane's On-Line Encyclopedia
@@ -22,8 +22,10 @@
 
 Executable sloane
   Main-is:             sloane.hs
-  Other-Modules:       Sloane.Config
-                       Sloane.DB
+  Other-Modules:       Sloane.DB
+                       Sloane.Config
+                       Sloane.GF
+                       Sloane.Transform
   ghc-options:         -Wall
   Build-depends:       base >=3 && <5,
                        containers >=0.5,
diff --git a/sloane.hs b/sloane.hs
--- a/sloane.hs
+++ b/sloane.hs
@@ -3,6 +3,9 @@
 -- Maintainer  : Anders Claesson <anders.claesson@gmail.com>
 -- License     : BSD-3
 --
+
+import           Data.List                    (intercalate)
+import           Data.Monoid
 import           Data.Text                    (Text)
 import qualified Data.Text                    as T
 import qualified Data.Text.IO                 as IO
@@ -11,143 +14,148 @@
 import           Network.Curl.Download        (openURI)
 import           Options.Applicative
 import           Sloane.Config
-import           Sloane.DB                    hiding (null)
+import           Sloane.DB                    (DB)
 import qualified Sloane.DB                    as DB
+import           Sloane.Transform
 
 type URL = String
 type Seq = Text
 
-data Visibility = Visible | Internal
-
-data Options
-    = Cmd Command
-    | IntOpts SearchOpts -- Internal opts for fallback to lookup
-
-data Command
-    = Lookup SearchOpts
-    | Grep   SearchOpts
-    | Filter FilterOpts
-    | Update
-    | Version
-
-data SearchOpts = SearchOpts
-    { full   :: Bool     -- Print all fields?
-    , keys   :: String   -- Keys of fields to print
-    , limit  :: Int      -- Fetch at most this many entries
-    , url    :: Bool     -- Print URLs of found entries
-    , terms  :: [String] -- Search terms
-    }
-
-data FilterOpts = FilterOpts
-    { invert :: Bool     -- Return sequences NOT in DB
+data Options = Options
+    { full    :: Bool     -- Print all fields?
+    , keys    :: String   -- Keys of fields to print
+    , limit   :: Int      -- Fetch at most this many entries
+    , url     :: Bool     -- Print URLs of found entries
+    , local   :: Bool     -- Lookup in local DB
+    , filtr   :: Bool     -- Filter out sequences in local DB
+    , invert  :: Bool     -- Return sequences NOT in DB
+    , transform :: String -- Apply the named transform
+    , listTransforms :: Bool -- List the names of all transforms
+    , update  :: Bool     -- Updated local DB
+    , version :: Bool     -- Show version info
+    , terms   :: [String] -- Search terms
     }
 
 oeisKeys :: String
 oeisKeys = "ISTUVWXNDHFYAOEeptoKC" -- Valid OEIS keys
 
 oeisUrls :: Config -> DB -> [URL]
-oeisUrls cfg = map ((oeisHost cfg ++) . T.unpack) . aNumbers
+oeisUrls cfg = map ((oeisHost cfg ++) . T.unpack) . DB.aNumbers
 
-oeisLookup :: SearchOpts -> Config -> IO DB
+oeisLookup :: Options -> Config -> IO DB
 oeisLookup opts cfg =
-    (parseOEISEntries . decodeUtf8 . either error id) <$>
+    (DB.parseOEISEntries . decodeUtf8 . either error id) <$>
     openURI (oeisURL cfg ++ "&" ++ urlEncodeVars [("n", show n), ("q", q)])
   where
     n = limit opts
     q = unwords $ terms opts
 
-grepDB :: SearchOpts -> DB -> DB
+grepDB :: Options -> DB -> DB
 grepDB opts = DB.take n . DB.grep (T.pack q)
   where
     n = limit opts
-    q = unwords $ terms opts
+    q = intercalate "," (terms opts)
 
-filterDB :: FilterOpts -> DB -> IO [Seq]
+applyTransform :: Options -> String -> IO ()
+applyTransform opts tname =
+    case lookupTranform tname of
+      Nothing -> error "No transform with that name"
+      Just f  -> case f $$ input of
+                   [] -> return ()
+                   cs -> putStrLn (showSeq cs)
+  where
+    tr c  = if c `elem` ";," then ' ' else c
+    input = map read (words (map tr (unwords (terms opts))))
+
+
+dropComment :: Text -> Text
+dropComment = T.takeWhile (/= '#')
+
+showSeq :: [Integer] -> String
+showSeq = intercalate "," . map show
+
+mkSeq :: Text -> Seq
+mkSeq = T.intercalate (T.pack ",") . T.words . clean . dropComment
+  where
+    clean = T.filter (`elem` " 0123456789-") . T.map tr
+    tr c  = if c `elem` ";," then ' ' else c
+
+filterDB :: Options -> DB -> IO [Seq]
 filterDB opts db = filter match . parseSeqs <$> IO.getContents
   where
     match q = (if invert opts then id else not) (DB.null $ DB.grep q db)
     parseSeqs = filter (not . T.null) . map mkSeq . T.lines
-    mkSeq = normalize . dropComment
-    dropComment = T.takeWhile (/= '#')
-    normalize = T.intercalate (T.pack ",") . T.words . clean . T.map tr
-    tr c = if c `elem` ";," then ' ' else c
-    clean = T.filter (`elem` " 0123456789-")
 
-searchOptionsParser :: Visibility -> Parser SearchOpts
-searchOptionsParser visibility = hiddenHelp <*> (SearchOpts
+hiddenHelp :: Parser (a -> a)
+hiddenHelp = abortOption ShowHelpText $ hidden <> long "help"
+
+optionsParser :: Parser Options
+optionsParser = hiddenHelp <*> (Options
     <$> switch
         ( short 'a'
        <> long "all"
-       <> help "Print all fields"
-       <> f )
+       <> help "Print all fields" )
     <*> strOption
         ( short 'k'
        <> metavar "KEYS"
        <> value "SN"
-       <> help "Keys of fields to print [default: SN]"
-       <> f )
+       <> help "Keys of fields to print [default: SN]" )
     <*> option auto
         ( short 'n'
        <> metavar "N"
        <> value 5
-       <> help "Fetch at most this many entries [default: 5]"
-       <> f )
+       <> help "Fetch at most this many entries [default: 5]" )
     <*> switch
         ( long "url"
-       <> help "Print URLs of found entries"
-       <> f )
-    <*> some (argument str (metavar "TERMS...")))
-  where
-    f = case visibility of {Visible -> idm; Internal -> internal}
-
-filterOptionsParser :: Parser FilterOpts
-filterOptionsParser = FilterOpts
-    <$> switch (long "invert" <> help "Return sequences NOT in the database")
-
-commandParser :: Parser Command
-commandParser = subparser
-    ( command "lookup" (info (Lookup <$> searchOptionsParser Visible)
-      ( progDesc "Lookup a sequence, or other search term, in OEIS" ))
-   <> command "grep" (info (Grep <$> searchOptionsParser Visible)
-      ( progDesc "Grep for a sequence in the local database" ))
-   <> command "filter" (info (Filter <$> filterOptionsParser)
-      ( progDesc ("Read sequences from stdin and "
-               ++ "return those that are in the local database")))
-   <> command "update" (info (pure Update)
-      ( progDesc "Update the local database" ))
-   <> command "version" (info (pure Version)
-      ( progDesc "Show version info" ))
-    )
-
-optionsParser :: Parser Options
-optionsParser =
-    (Cmd <$> commandParser) <|> (IntOpts <$> searchOptionsParser Internal)
-
-runSearch :: (SearchOpts -> Config -> IO DB) -> SearchOpts -> Config -> IO ()
-runSearch f opts cfg = f opts cfg >>=
-    if url opts
-        then putStr . unlines . oeisUrls cfg
-        else putDB cfg (if full opts then oeisKeys else keys opts)
-
-runCmd :: Command -> Config -> IO ()
-runCmd (Lookup opts) = runSearch oeisLookup opts
-runCmd (Grep   opts) = runSearch (\o cfg -> grepDB o <$> readDB cfg) opts
-runCmd (Filter opts) = \c -> readDB c >>= filterDB opts >>= mapM_ IO.putStrLn
-runCmd Update        = initDB
-runCmd Version       = putStrLn . name
+       <> help "Print URLs of found entries" )
+    <*> switch
+        ( long "local"
+       <> help "Use the local database rather than oeis.org" )
+    <*> switch
+        ( long "filter"
+       <> help ("Read sequences from stdin and return"
+            ++ " those that are in the local database") )
+    <*> switch
+        ( long "invert"
+       <> help ("Return sequences NOT in the database;"
+            ++ " only relevant when used with --filter") )
+    <*> strOption
+        ( long "transform"
+       <> metavar "NAME"
+       <> value ""
+       <> help ("Apply the named transform to input sequence"))
+    <*> switch
+        ( long "list-transforms"
+       <> help "List the names of all transforms" )
+    <*> switch
+        ( long "update"
+       <> help "Update the local database" )
+    <*> switch
+        ( long "version"
+       <> help "Show version info" )
+    <*> many (argument str (metavar "TERMS...")))
 
-hiddenHelp :: Parser (a -> a)
-hiddenHelp = abortOption ShowHelpText $ hidden <> short 'h' <> long "help"
+search :: (Options -> Config -> IO DB) -> Options -> Config -> IO ()
+search f opts cfg = f opts cfg >>= put
+  where
+    put | url opts  = putStr . unlines . oeisUrls cfg
+        | otherwise = DB.put cfg $ if full opts then oeisKeys else keys opts
 
 main :: IO ()
 main = do
-    conf <- defaultConfig
-    opts <- customExecParser preferences (info parser description)
-    case opts of
-        (Cmd cmd)   -> runCmd cmd conf
-        (IntOpts o) -> runCmd (Lookup o) conf -- Fallback to 'lookup'
-  where
-    parser = hiddenHelp <*> optionsParser
-    preferences = prefs showHelpOnError
-    description = fullDesc <> footer
-        "Run 'sloane COMMAND --help' for help on a specific command."
+    let pprefs = prefs mempty
+    let pinfo = info optionsParser fullDesc
+    let usage = handleParseResult . Failure
+         $ parserFailure pprefs pinfo ShowHelpText mempty
+    opts <- customExecParser pprefs pinfo
+    let tname = transform opts
+    let sloane
+         | version opts = putStrLn . nameVer
+         | update opts = DB.update
+         | listTransforms opts = const $ mapM_ (putStrLn . name) transforms
+         | filtr opts = \c -> DB.read c >>= filterDB opts >>= mapM_ IO.putStrLn
+         | null (terms opts) = const usage
+         | not (null tname) = const $ applyTransform opts tname
+         | local opts = search (\o cfg -> grepDB o <$> DB.read cfg) opts
+         | otherwise = search oeisLookup opts
+    defaultConfig >>= sloane
