diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Version history for hwk
 
+## 0.6 (2021-06-14)
+- add --words mode: applies function to list of words in each line
+- experimental shell mode
+- initial config fixes
+
 ## 0.5 (2020-10-13)
 - file arguments can now provide input
 - experimental --run mode to execute IO
diff --git a/Main.hs b/Main.hs
deleted file mode 100644
--- a/Main.hs
+++ /dev/null
@@ -1,264 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-#if !MIN_VERSION_simple_cmd_args(0,1,4)
-import Control.Applicative (
-#if !MIN_VERSION_simple_cmd_args(0,1,3)
-    (<|>),
-#endif
-    many)
-#endif
-import Control.Monad.Extra
-import qualified Data.List.Extra as L
-import Data.Version (showVersion)
-import Language.Haskell.Interpreter
-import SimpleCmdArgs
-import System.Directory
-import System.FilePath
-import System.IO (hPutStrLn, stderr)
-
-import Paths_hwk (getDataDir, version)
-
-data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode | RunMode
-  deriving Eq
-
-main :: IO ()
-main = do
-  userdir <- getXdgDirectory XdgConfig "hwk"
-  simpleCmdArgs (Just version) "A Haskell awk/sed like tool"
-    "Simple shell text processing with Haskell" $
-    runExpr userdir <$> modeOpt <*> cfgdirOpt userdir <*> strArg "FUNCTION" <*> many (strArg "FILE...")
-  where
-    modeOpt :: Parser HwkMode
-    modeOpt =
-      flagWith' LineMode 'l' "line" "Apply function to each line" <|>
-      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>
-      flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>
-      flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>
-      flagWith DefaultMode RunMode 'r' "run" "Run Haskell IO"
-
-    cfgdirOpt :: String -> Parser (Maybe FilePath)
-    cfgdirOpt dir =
-      optional (normalise <$> strOptionWith 'c' "config-dir" "DIR" ("Override the config dir [default:" ++ dir ++ "]"))
-
-runExpr :: FilePath -> HwkMode -> Maybe FilePath -> String -> [FilePath] -> IO ()
-runExpr userdir mode mcfgdir stmt files = do
-  mapM_ checkFileExists files
-  cfgdir <-
-    case mcfgdir of
-      Just dir -> do
-        unlessM (doesDirectoryExist dir) $
-          error' $ dir ++ ": directory not found"
-        return dir
-      Nothing -> do
-        let usercfg = userdir </> "Hwk.hs"
-        datadir <- getDataDir
-        let versionCfg = usercfg ++ "-" ++ showVersion version
-        unlessM (doesFileExist versionCfg) $
-          copyFile (datadir </> "Hwk.hs") versionCfg
-        unlessM (doesFileExist usercfg) $ do
-          copyFile (datadir </> "Hwk.hs") usercfg
-          warn $ usercfg ++ " created"
-        return userdir
-  runInterpreter (runHint cfgdir) >>=
-    either (putStrLn . errorString) return
-  where
-    runHint :: FilePath -> Interpreter ()
-    runHint cfgdir = do
-      -- could ignore this for eval or typecheck
-      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]
-      loadModules ["Hwk"]
-      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))
-      setHwkImports ["Prelude"]
-      imports <- do
-        haveModules <- typeChecks "userModules"
-        if haveModules
-          then interpret "userModules" infer
-          else return ["Prelude", "Data.List"]
-      setHwkImports imports
-      let inputs = if null files then ["-"] else files
-      case mode of
-        DefaultMode ->
-          withInputFiles inputs (mapInputList stmt . lines)
-        LineMode ->
-          withInputFiles inputs (mapEachLine stmt . lines)
-        WholeMode -> do
-          withInputFiles inputs (applyToInput stmt . removeTrailingNewline)
-        -- FIXME take or warn about args
-        TypeMode -> typeOfExpr stmt
-        EvalMode -> evalExpr stmt
-        RunMode -> execExpr stmt
-      where
-        withInputFiles :: [FilePath] -> (String -> Interpreter ())
-                       -> Interpreter ()
-        withInputFiles inputs interp = do
-          forM_ inputs $ \ file ->
-            liftIO (if file == "-" then getContents else readFile file) >>=
-            interp
-
-        removeTrailingNewline :: String -> String
-        removeTrailingNewline "" = ""
-        removeTrailingNewline s =
-          if last s == '\n'
-          then init s
-          else s
-
-    checkFileExists :: FilePath -> IO ()
-    checkFileExists "-" = return ()
-    checkFileExists file = do
-      unlessM (doesFileExist file) $
-        error' $ "file not found: " ++ file
-
-    errorString :: InterpreterError -> String
-    errorString (WontCompile es) =
-      unlines $ "ERROR: Won't compile:" : map errMsg es
-    errorString e = show e
-
-cleanupType :: String -> String
-cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"
-
--- fn $ lines input
-mapInputList :: String -> [String] -> Interpreter ()
-mapInputList stmt inputs = do
-  typ <- resultTypeOfApplied stmt "[String]"
-  case cleanupType typ of
-    "String" -> do
-      fn <- interpret stmt (as :: [String] -> String)
-      liftIO $ putStrLn (fn inputs)
-    "[String]" -> do
-      fn <- interpret stmt (as :: [String] -> [String])
-      liftIO $ mapM_ putStrLn (fn inputs)
-    "[[String]]" -> do
-      fn <- interpret stmt (as :: [String] -> [[String]])
-      liftIO $ mapM_ (putStrLn . unwords) (fn inputs)
-    "Int" -> do
-      fn <- interpret stmt (as :: [String] -> Int)
-      liftIO $ print (fn inputs)
-    "[Int]" -> do
-      fn <- interpret stmt (as :: [String] -> [Int])
-      liftIO $ mapM_ print (fn inputs)
-    "[[Int]]" -> do
-      fn <- interpret stmt (as :: [String] -> [[Int]])
-      liftIO $ mapM_ (putStrLn . unwords . map show) (fn inputs)
-    _ -> do
-      liftIO $ warn typ
-      fn <- interpret stmt (as :: [String] -> [String])
-      liftIO $ mapM_ putStrLn (fn inputs)
-
--- map fn $ lines input
-mapEachLine :: String -> [String] -> Interpreter ()
-mapEachLine stmt inputs = do
-  typ <- resultTypeOfApplied stmt "String"
-  case cleanupType typ of
-    "String" -> do
-      fn <- interpret stmt (as :: String -> String)
-      liftIO $ mapM_ (putStrLn . fn) inputs
-    "[String]" -> do
-      fn <- interpret stmt (as :: String -> [String])
-      liftIO $ mapM_ (putStrLn . unwords . fn) inputs
-    "[[String]]" -> do
-      fn <- interpret stmt (as :: String -> [[String]])
-      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs
-    "Int" -> do
-      fn <- interpret stmt (as :: String -> Int)
-      liftIO $ mapM_ (print . fn) inputs
-    "[Int]" -> do
-      fn <- interpret stmt (as :: String -> [Int])
-      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs
-    "[[Int]]" -> do
-      fn <- interpret stmt (as :: String -> [[Int]])
-      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs
-    _ -> do
-      liftIO $ warn typ
-      fn <- interpret stmt (as :: String -> String)
-      liftIO $ mapM_ (putStrLn . fn) inputs
-
--- fn input
-applyToInput :: String -> String -> Interpreter ()
-applyToInput stmt input = do
-  typ <- resultTypeOfApplied stmt "String"
-  case cleanupType typ of
-    "String" -> do
-      fn <- interpret stmt (as :: String -> String)
-      liftIO $ putStrLn (fn input)
-    "[String]" -> do
-      fn <- interpret stmt (as :: String -> [String])
-      liftIO $ mapM_ putStrLn (fn input)
-    "[[String]]" -> do
-      fn <- interpret stmt (as :: String -> [[String]])
-      liftIO $ mapM_ (putStrLn . unwords) (fn input)
-    "Int" -> do
-      fn <- interpret stmt (as :: String -> Int)
-      liftIO $ print (fn input)
-    "[Int]" -> do
-      fn <- interpret stmt (as :: String -> [Int])
-      liftIO $ mapM_ print (fn input)
-    "[[Int]]" -> do
-      fn <- interpret stmt (as :: String -> [[Int]])
-      liftIO $ mapM_ (putStrLn . unwords . map show) (fn input)
-    _ -> do
-      liftIO $ warn typ
-      fn <- interpret stmt (as :: String -> [String])
-      liftIO $ mapM_ putStrLn (fn input)
-
-typeOfExpr :: String -> Interpreter ()
-typeOfExpr stmt = do
-#if MIN_VERSION_hint(0,8,0)
-  etypchk <- typeChecksWithDetails stmt
-  case etypchk of
-    Left err -> liftIO $ mapM_ (putStrLn . errMsg) err
-    Right typ -> liftIO $ putStrLn (cleanupType typ)
-#else
-  typeOf stmt >>= liftIO . putStrLn . cleanupType
-#endif
-
-evalExpr :: String -> Interpreter ()
-evalExpr stmt = do
-  typ <- typeOf stmt
-  case cleanupType typ of
-    "String" -> do
-      interpret stmt "a String" >>= liftIO . putStrLn
-    "[String]" -> do
-      interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn
-    "[[String]]" -> do
-      interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)
-    "Int" -> do
-      interpret stmt (1 :: Int) >>= liftIO . print
-    "[Int]" -> do
-      interpret stmt [1 :: Int] >>= liftIO . mapM_ print
-    "[[Int]]" -> do
-      interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)
-    _ ->
-      if " -> " `L.isInfixOf` typ
-        then liftIO $ putStrLn typ
-        else
-        -- FIXME option to display type
-        eval stmt >>= liftIO . putStrLn
-
--- FIXME add --safe?
-execExpr :: String -> Interpreter ()
-execExpr stmt = do
-  typ <- typeOf stmt
-  case cleanupType typ of
-    "IO ()" -> runStmt stmt
-    "IO String" ->
-      runStmt $ stmt ++ ">>= putStrLn"
-    "IO [String]" ->
-      runStmt $ stmt ++ ">>= mapM_ putStrLn"
-    "IO [[String]]" ->
-      runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"
-    _ -> liftIO $ warn typ
-
-resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String
-resultTypeOfApplied expr typ =
-  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")
-
-warn :: String -> IO ()
-warn = hPutStrLn stderr
-
--- from simple-cmd
-error' :: String -> a
-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))
-error' = errorWithoutStackTrace
-#else
-error' = error
-#endif
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
 # hwk ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)
 
-<img align="right" alt="hwk" src="hwk.png" />
-
 **hwk** (pronounced "hawk") is a simple Haskell-based commandline text processing tool, somewhat similar to tools like *awk*, *grep*, *sed*.
 `hwk` applies composed pure Haskell functions to a list of lines of input, enabling text processing without having to remember an obscure DSL or awkward cli options. This tool can also help to encourage people to think functionally.
 
+<img align="right" alt="[hawk image]" src="hwk.png" />
+
 hwk was originally written by Lukas Martinelli in 2016-2017:
 see the [original README file](README.md.orig).
 
@@ -126,10 +126,18 @@
 - `hwk` use the hint library to apply haskell functions to input.
 - By default it splits the input to a list of lines and applies the function to them
 - Use `-a` or `--all` to apply a function to all the input,
-  or `-l`/`--line` to map the function on each line separately.
+  or `-l`/`--line` to map the function on each line separately,
+  or `-w`/`--words` to map the function on each line of words,
 - If you pass file arguments, their contents will be read and passed to the function.
 - You can also typecheck the function or an expression with `-t`/`--typecheck`,
   evaluate an expr with `-e`/`--eval`, or `-r`/`--run` an IO statement.
+
+Note the equivalences:
+- `hwk 'f . unlines'` == `hwk -a f`
+- `hwk 'map f'` == `hwk -l f`
+- `hwk -a 'f . lines'` == `hwk f`
+- `hwk -l 'f . words'` == `hwk -w f`
+- `hwk 'map (f . words)'` == `hwk -w f`
 
 ## Supported return types
 
diff --git a/hwk.cabal b/hwk.cabal
--- a/hwk.cabal
+++ b/hwk.cabal
@@ -1,5 +1,5 @@
 name:                hwk
-version:             0.5
+version:             0.6
 synopsis:            Commandline text processing with Haskell functions
 description:
             A commandline tool for text processing with Haskell functions,
@@ -12,7 +12,7 @@
 author:              Lukas Martinelli
 maintainer:          Jens Petersen <juhpetersen@gmail.com>
 copyright:           2016-2017 Lukas Martinelli,
-                     2020 Jens Petersen
+                     2020-2021 Jens Petersen
 category:            Development
 build-type:          Simple
 data-files:          Hwk.hs
@@ -27,8 +27,12 @@
 
 executable hwk
   main-is:             Main.hs
-  other-modules:       Paths_hwk
+  hs-source-dirs:      src
   autogen-modules:     Paths_hwk
+  other-modules:       Paths_hwk
+                       Common
+                       Hwk.IO
+                       Hwk.Types
   build-depends:       base <5,
                        directory >= 1.2.3.0,
                        extra,
diff --git a/src/Common.hs b/src/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Common.hs
@@ -0,0 +1,14 @@
+module Common (
+  setNoBuffering,
+  warn
+  )
+where
+
+import System.IO
+
+warn :: String -> IO ()
+warn = hPutStrLn stderr
+
+setNoBuffering :: IO ()
+setNoBuffering =
+  hSetBuffering stdout NoBuffering
diff --git a/src/Hwk/IO.hs b/src/Hwk/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Hwk/IO.hs
@@ -0,0 +1,46 @@
+module Hwk.IO (execExpr, shellSession)
+where
+
+import Language.Haskell.Interpreter
+
+import Common
+import Hwk.Types
+
+-- FIXME add --safe?
+execExpr :: String -> Interpreter ()
+execExpr stmt = do
+  typ <- typeOf stmt
+  case cleanupType typ of
+    "IO ()" -> runStmt stmt
+    "IO String" ->
+      runStmt $ stmt ++ ">>= putStrLn"
+    "IO [String]" ->
+      runStmt $ stmt ++ ">>= mapM_ putStrLn"
+    "IO [[String]]" ->
+      runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"
+    _ -> liftIO $ warn typ
+
+shellSession :: Interpreter ()
+shellSession = do
+  liftIO $ putStr ">= "
+  stmt <- liftIO getLine
+  if null (words stmt) then shellSession
+    else do
+    etypchk <- typeChecksWithDetails stmt
+    case etypchk of
+      Left err -> do
+        liftIO $ mapM_ (putStrLn . errMsg) err
+        runStmt stmt
+        shellSession
+      Right typ -> do
+        liftIO $ putStrLn $ ":: " ++ cleanupType typ
+        case cleanupType typ of
+          "IO ()" -> runStmt stmt
+          "IO String" ->
+            runStmt $ stmt ++ ">>= putStrLn"
+          "IO [String]" ->
+            runStmt $ stmt ++ ">>= mapM_ putStrLn"
+          "IO [[String]]" ->
+            runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"
+          _ -> eval stmt >>= liftIO . putStrLn
+        shellSession
diff --git a/src/Hwk/Types.hs b/src/Hwk/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Hwk/Types.hs
@@ -0,0 +1,6 @@
+module Hwk.Types (cleanupType) where
+
+import qualified Data.List.Extra as L
+
+cleanupType :: String -> String
+cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,279 @@
+{-# LANGUAGE CPP #-}
+
+#if !MIN_VERSION_simple_cmd_args(0,1,4)
+import Control.Applicative (
+#if !MIN_VERSION_simple_cmd_args(0,1,3)
+    (<|>),
+#endif
+    many)
+#endif
+import Control.Monad.Extra
+import qualified Data.List.Extra as L
+import Data.Version (showVersion)
+import Language.Haskell.Interpreter
+import SimpleCmdArgs
+import System.Directory
+import System.FilePath
+
+import Common
+import Hwk.IO
+import Hwk.Types
+import Paths_hwk (getDataDir, version)
+
+data HwkMode = DefaultMode | LineMode | WordsMode | WholeMode | TypeMode | EvalMode | RunMode | ShellMode
+  deriving Eq
+
+main :: IO ()
+main = do
+  userdir <- getXdgDirectory XdgConfig "hwk"
+  simpleCmdArgs (Just version) "A Haskell awk/sed like tool"
+    "Simple shell text processing with Haskell" $
+    runExpr userdir <$> modeOpt <*> cfgdirOpt userdir <*> strArg "FUNCTION" <*> many (strArg "FILE...")
+  where
+    modeOpt :: Parser HwkMode
+    modeOpt =
+      flagWith' LineMode 'l' "line" "Apply function to each line" <|>
+      flagWith' WordsMode 'w' "words" "Apply function to list of words per line" <|>
+      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>
+      flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>
+      flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>
+      flagWith' RunMode 'r' "run" "Run Haskell IO" <|>
+      flagWith DefaultMode ShellMode 's' "shell" "Haskell Shell"
+
+    cfgdirOpt :: String -> Parser (Maybe FilePath)
+    cfgdirOpt dir =
+      optional (normalise <$> strOptionWith 'c' "config-dir" "DIR" ("Override the config dir [default:" ++ dir ++ "]"))
+
+runExpr :: FilePath -> HwkMode -> Maybe FilePath -> String -> [FilePath] -> IO ()
+runExpr userdir mode mcfgdir stmt files = do
+  mapM_ checkFileExists files
+  cfgdir <-
+    case mcfgdir of
+      Just dir -> do
+        unlessM (doesDirectoryExist dir) $
+          error' $ dir ++ ": directory not found"
+        return dir
+      Nothing -> do
+        let usercfg = userdir </> "Hwk.hs"
+        datadir <- getDataDir
+        let datafile = datadir </> "Hwk.hs"
+        unlessM (doesFileExist datafile) $
+          error' $ "default config file does not exist: " ++ datafile
+        createDirectoryIfMissing True userdir
+        let versionCfg = usercfg ++ "-" ++ showVersion version
+        unlessM (doesFileExist versionCfg) $
+          copyFile datafile versionCfg
+        unlessM (doesFileExist usercfg) $ do
+          copyFile datafile usercfg
+          warn $ usercfg ++ " created"
+        return userdir
+  runInterpreter (runHint cfgdir) >>=
+    either (putStrLn . errorString) return
+  where
+    runHint :: FilePath -> Interpreter ()
+    runHint cfgdir = do
+      -- could ignore this for eval or typecheck
+      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]
+      loadModules ["Hwk"]
+      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))
+      setHwkImports ["Prelude"]
+      imports <- do
+        haveModules <- typeChecks "userModules"
+        if haveModules
+          then interpret "userModules" infer
+          else return ["Prelude", "Data.List"]
+      setHwkImports imports
+      let inputs = if null files then ["-"] else files
+      case mode of
+        DefaultMode ->
+          withInputFiles inputs (mapInputList stmt . lines)
+        LineMode ->
+          withInputFiles inputs (mapEachLine stmt . lines)
+        WordsMode ->
+          withInputFiles inputs (mapWordsLine stmt . map words . lines)
+        WholeMode -> do
+          withInputFiles inputs (applyToInput stmt . removeTrailingNewline)
+        -- FIXME take or warn about args
+        TypeMode -> typeOfExpr stmt
+        EvalMode -> evalExpr stmt
+        RunMode -> execExpr stmt
+        ShellMode -> liftIO setNoBuffering >> shellSession
+      where
+        withInputFiles :: [FilePath] -> (String -> Interpreter ())
+                       -> Interpreter ()
+        withInputFiles inputs interp = do
+          forM_ inputs $ \ file ->
+            liftIO (if file == "-" then getContents else readFile file) >>=
+            interp
+
+        removeTrailingNewline :: String -> String
+        removeTrailingNewline "" = ""
+        removeTrailingNewline s =
+          if last s == '\n'
+          then init s
+          else s
+
+    checkFileExists :: FilePath -> IO ()
+    checkFileExists "-" = return ()
+    checkFileExists file = do
+      unlessM (doesFileExist file) $
+        error' $ "file not found: " ++ file
+
+    errorString :: InterpreterError -> String
+    errorString (WontCompile es) =
+      unlines $ "ERROR: Won't compile:" : map errMsg es
+    errorString e = show e
+
+-- fn $ lines input
+mapInputList :: String -> [String] -> Interpreter ()
+mapInputList stmt inputs = do
+  typ <- resultTypeOfApplied stmt "[String]"
+  case cleanupType typ of
+    "String" -> do
+      fn <- interpret stmt (as :: [String] -> String)
+      liftIO $ putStrLn (fn inputs)
+    "[String]" -> do
+      fn <- interpret stmt (as :: [String] -> [String])
+      liftIO $ mapM_ putStrLn (fn inputs)
+    "[[String]]" -> do
+      fn <- interpret stmt (as :: [String] -> [[String]])
+      liftIO $ mapM_ (putStrLn . unwords) (fn inputs)
+    "Int" -> do
+      fn <- interpret stmt (as :: [String] -> Int)
+      liftIO $ print (fn inputs)
+    "[Int]" -> do
+      fn <- interpret stmt (as :: [String] -> [Int])
+      liftIO $ mapM_ print (fn inputs)
+    "[[Int]]" -> do
+      fn <- interpret stmt (as :: [String] -> [[Int]])
+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn inputs)
+    _ -> do
+      liftIO $ warn typ
+      fn <- interpret stmt (as :: [String] -> [String])
+      liftIO $ mapM_ putStrLn (fn inputs)
+
+-- map fn $ lines input
+mapEachLine :: String -> [String] -> Interpreter ()
+mapEachLine stmt inputs = do
+  typ <- resultTypeOfApplied stmt "String"
+  case cleanupType typ of
+    "String" -> do
+      fn <- interpret stmt (as :: String -> String)
+      liftIO $ mapM_ (putStrLn . fn) inputs
+    "[String]" -> do
+      fn <- interpret stmt (as :: String -> [String])
+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs
+    "[[String]]" -> do
+      fn <- interpret stmt (as :: String -> [[String]])
+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs
+    "Int" -> do
+      fn <- interpret stmt (as :: String -> Int)
+      liftIO $ mapM_ (print . fn) inputs
+    "[Int]" -> do
+      fn <- interpret stmt (as :: String -> [Int])
+      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs
+    "[[Int]]" -> do
+      fn <- interpret stmt (as :: String -> [[Int]])
+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs
+    _ -> do
+      liftIO $ warn typ
+      fn <- interpret stmt (as :: String -> String)
+      liftIO $ mapM_ (putStrLn . fn) inputs
+
+-- map fn $ lines input
+mapWordsLine :: String -> [[String]] -> Interpreter ()
+mapWordsLine stmt inputs = do
+  typ <- resultTypeOfApplied stmt "[String]"
+  case cleanupType typ of
+    "String" -> do
+      fn <- interpret stmt (as :: [String] -> String)
+      liftIO $ mapM_ (putStrLn . fn) inputs
+    "[String]" -> do
+      fn <- interpret stmt (as :: [String] -> [String])
+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs
+    "[[String]]" -> do
+      fn <- interpret stmt (as :: [String] -> [[String]])
+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs
+    "Int" -> do
+      fn <- interpret stmt (as :: [String] -> Int)
+      liftIO $ mapM_ (print . fn) inputs
+    "[Int]" -> do
+      fn <- interpret stmt (as :: [String] -> [Int])
+      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs
+    "[[Int]]" -> do
+      fn <- interpret stmt (as :: [String] -> [[Int]])
+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs
+    _ -> do
+      liftIO $ warn typ
+      fn <- interpret stmt (as :: [String] -> [String])
+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs
+
+-- fn input
+applyToInput :: String -> String -> Interpreter ()
+applyToInput stmt input = do
+  typ <- resultTypeOfApplied stmt "String"
+  case cleanupType typ of
+    "String" -> do
+      fn <- interpret stmt (as :: String -> String)
+      liftIO $ putStrLn (fn input)
+    "[String]" -> do
+      fn <- interpret stmt (as :: String -> [String])
+      liftIO $ mapM_ putStrLn (fn input)
+    "[[String]]" -> do
+      fn <- interpret stmt (as :: String -> [[String]])
+      liftIO $ mapM_ (putStrLn . unwords) (fn input)
+    "Int" -> do
+      fn <- interpret stmt (as :: String -> Int)
+      liftIO $ print (fn input)
+    "[Int]" -> do
+      fn <- interpret stmt (as :: String -> [Int])
+      liftIO $ mapM_ print (fn input)
+    "[[Int]]" -> do
+      fn <- interpret stmt (as :: String -> [[Int]])
+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn input)
+    _ -> do
+      liftIO $ warn typ
+      fn <- interpret stmt (as :: String -> [String])
+      liftIO $ mapM_ putStrLn (fn input)
+
+resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String
+resultTypeOfApplied expr typ =
+  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")
+
+typeOfExpr :: String -> Interpreter ()
+typeOfExpr stmt = do
+  etypchk <- typeChecksWithDetails stmt
+  case etypchk of
+    Left err -> liftIO $ mapM_ (putStrLn . errMsg) err
+    Right typ -> liftIO $ putStrLn (cleanupType typ)
+
+evalExpr :: String -> Interpreter ()
+evalExpr stmt = do
+  typ <- typeOf stmt
+  case cleanupType typ of
+    "String" -> do
+      interpret stmt "a String" >>= liftIO . putStrLn
+    "[String]" -> do
+      interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn
+    "[[String]]" -> do
+      interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)
+    "Int" -> do
+      interpret stmt (1 :: Int) >>= liftIO . print
+    "[Int]" -> do
+      interpret stmt [1 :: Int] >>= liftIO . mapM_ print
+    "[[Int]]" -> do
+      interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)
+    _ ->
+      if " -> " `L.isInfixOf` typ
+        then liftIO $ putStrLn typ
+        else
+        -- FIXME option to display type
+        eval stmt >>= liftIO . putStrLn
+
+-- from simple-cmd
+error' :: String -> a
+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))
+error' = errorWithoutStackTrace
+#else
+error' = error
+#endif
