diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,13 @@
 # Version history for hwk
 
-## 0.2.1 (2020-10-11)
+## 0.4 (2020-10-12)
+- add --eval mode
+- refactor: drop ToList and ToString classes
+- import Data.List.Extra by default
+- --all: remove trailing newline
+- add examples/
+
+## 0.3 (2020-10-11)
 - add --line mode: takes a function on a String - applied to every line
   - uses new ToString class (not ToList)
 - add --type-check: prints the type of a given function
diff --git a/Config.hs b/Config.hs
deleted file mode 100644
--- a/Config.hs
+++ /dev/null
@@ -1,56 +0,0 @@
-{-# LANGUAGE FlexibleInstances #-}
-
-module Hwk where
-
-import Data.List (intercalate)
-
--- | modules to be imported into hint
--- these modules must be from installed packages
-userModules :: [String]
-userModules = [ "Prelude", "Data.List", "Data.Char", "Data.Bool"
-              -- , "Data.List.Extra", "Data.Tuple.Extra"
-              , "System.FilePath"
-              ]
-
--- you can put user hwk functions here:
-
-
-int :: String -> Int
-int str = read str :: Int
-
-ints :: [String] -> [Int]
-ints = map int
-
------- below here all required internal machinery ------
-
--- | toString and toList are required by hwk
--- They determine the types of functions hwk can interpret,
--- allowing some simple polymorphism.
-
--- ToString allows handling functions of type: ToString a => String -> a
-class ToString a where
-  toString :: a -> String
-instance ToString String where
-  toString x = x
-instance ToString [String] where
-  toString = unwords
-instance ToString [[String]] where
-  toString = intercalate "\t" . map unwords
-instance ToString Int where
-  toString x = show x
-instance ToString [Int] where
-  toString lst = unwords $ map show lst
-
--- ToList allows handling functions of type: ToList a => [String] -> a
-class ToList a where
-  toList :: a -> [String]
-instance ToList String where
-  toList x = [x]
-instance ToList [String] where
-  toList = id
-instance ToList [[String]] where
-  toList = map (intercalate "\t")
-instance ToList Int where
-  toList x = [show x]
-instance ToList [Int] where
-  toList lst = map show lst
diff --git a/Hwk.hs b/Hwk.hs
new file mode 100644
--- /dev/null
+++ b/Hwk.hs
@@ -0,0 +1,15 @@
+module Hwk where
+
+-- | modules to be imported in hint interpreter
+-- These modules must be from installed packages
+userModules :: [String]
+userModules = [ "Prelude", "Data.Bool", "Data.Char", "Data.List"
+              , "Data.List.Extra", "Data.Tuple.Extra", "Data.Version.Extra"
+              , "System.FilePath"
+              ]
+
+int :: String -> Int
+int str = read str :: Int
+
+ints :: [String] -> [Int]
+ints = map int
diff --git a/Main.hs b/Main.hs
--- a/Main.hs
+++ b/Main.hs
@@ -14,7 +14,7 @@
 
 import Paths_hwk (getDataDir, version)
 
-data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode
+data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode
   deriving Eq
 
 main :: IO ()
@@ -25,7 +25,8 @@
   where
     modeOpt :: Parser HwkMode
     modeOpt =
-      flagWith' TypeMode 't' "type-check" "Print out the type of the given function" <|>
+      flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>
+      flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>
       flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>
       flagWith DefaultMode LineMode 'l' "line" "Apply function to each line"
 
@@ -38,9 +39,9 @@
   datadir <- getDataDir
   let versionCfg = usercfg ++ "-" ++ showVersion version
   unlessM (doesFileExist versionCfg) $
-    copyFile (datadir </> "Config.hs") versionCfg
+    copyFile (datadir </> "Hwk.hs") versionCfg
   unlessM (doesFileExist usercfg) $ do
-    copyFile (datadir </> "Config.hs") usercfg
+    copyFile (datadir </> "Hwk.hs") usercfg
     warn $ usercfg ++ " created"
   r <- runInterpreter (runHint userdir input)
   case r of
@@ -49,8 +50,9 @@
   where
     runHint :: FilePath -> String -> Interpreter ()
     runHint cfgdir input = do
-      set [searchPath := [cfgdir]]
+      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]
       loadModules ["Hwk"]
+      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))
       setHwkImports ["Prelude"]
       imports <- do
         haveModules <- typeChecks "userModules"
@@ -58,35 +60,19 @@
           then interpret "userModules" infer
           else return ["Prelude", "Data.List"]
       setHwkImports imports
-      if mode == TypeMode then 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
-        else do
-        let polyList = "toList . "
-            polyString = "toString . "
-        case mode of
-          DefaultMode -> do
-            fn <- interpret (polyList ++ stmt) (as :: [String] -> [String])
-            liftIO $ mapM_ putStrLn (fn (lines input))
-          LineMode -> do
-            fn <- interpret (polyString ++ stmt) (as :: String -> String)
-            liftIO $ mapM_ (putStrLn . fn) (lines input)
-          WholeMode -> do
-            fn <- interpret (polyList ++ stmt) (as :: String -> [String])
-            liftIO $ mapM_ putStrLn (fn input)
-          TypeMode -> error "already handled earlier"
+      case mode of
+        DefaultMode -> mapInputList stmt (lines input)
+        LineMode -> mapEachLine stmt (lines input)
+        WholeMode -> applyToInput stmt (removeTrailingNewline input)
+        TypeMode -> typeOfExpr stmt
+        EvalMode -> evalExpr stmt
       where
-        cleanupType :: String -> String
-        cleanupType = L.replace "[Char]" "String"
-
-    -- FIXME use Set
-    setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))
+        removeTrailingNewline :: String -> String
+        removeTrailingNewline "" = ""
+        removeTrailingNewline s =
+          if last s == '\n'
+          then init s
+          else s
 
     -- checkFileExists :: FilePath -> IO ()
     -- checkFileExists file = do
@@ -98,5 +84,129 @@
       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] -> InterpreterT IO ()
+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] -> InterpreterT IO ()
+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 -> InterpreterT IO ()
+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 -> InterpreterT IO ()
+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 -> InterpreterT IO ()
+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 . mapM_ print
+    "[[Int]]" -> do
+      interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)
+    _ -> do
+      if " -> " `L.isInfixOf` typ
+        then liftIO $ putStrLn typ
+        else do
+        res <- eval stmt
+        -- FIXME option to display type
+        liftIO $ putStrLn $ res ++ " :: " ++ typ
+
 warn :: String -> IO ()
 warn = hPutStrLn stderr
+
+resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String
+resultTypeOfApplied expr typ =
+  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,14 +1,23 @@
 # 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 text processing commandline tool, somewhat similar to tools like **awk**, **grep**, **sed**.
+`hwk` applies composed pure Haskell functions to a list of strings from stdin, enabling text processing without having to remember an obscure DSL or cli options. This tool can also help to encourage people to think functionally.
+
 hwk was originally written by Lukas Martinelli in 2016-2017:
 see the [original README file](README.md.orig).
 
-<img align="right" alt="hwk" src="hwk.png" />
+**hwk** is pretty similar to [**Hawk**](https://github.com/gelisam/hawk),
+so you may also want to try that for a different more sophisticated monadic
+implementation. Some of main differences are:
 
-**hwk** (pronounced "hawk") is a simple Haskell-based text stream manipulation tool, somewhat similar to tools like **awk** or **sed**.
-`hwk` applies concisely composed pure functions to a list of strings from stdin. Because Haskell is lazy and has a powerful arsenal of functions, there is no need to invent another DSL. Hopefully this tool will also encourage more people to think functionally.
+- hwk uses String for input for type simplicity, whereas hawk uses ByteString
+- hawk has special options for controlling input and output delimiters, whereas in hwk everything is roughly just `[String] -> [String]` (more details below)
+- by default hwk applies a function to the list of all the lines of stdin: `hwk -l` corresponds to `hawk -m` and `hawk -a` to `hwk`.
 
 ## Example
+Some simple use-cases are in the [examples](examples/) directory.
 
 Change and append a string to each line:
 ```bash
@@ -36,33 +45,34 @@
 
 Extract data from a file:
 ```bash
-$ cat /etc/passwd | hwk --line 'reverse . filter (/= "x") . take 3 . splitOn ":"' | head -3
+$ cat /etc/passwd | hwk -l 'reverse . filter (/= "x") . take 3 . splitOn ":"' | head -3
 0 root
 1 bin
 2 daemon
 ```
-(a module defining `splitOn` from the extra or split library needs to be added to the Hwk.hs config file).
+(uses `splitOn` from the extra library; `-l` is the short form of `--line`).
 
 The argument passed to `hwk` must be a valid Haskell function: a function that takes a list of strings and returns a new list or a single value.
 
-Check where input contains a certain string:
+Check whether the input contains a certain string:
 ```
 $ cat /etc/passwd | hwk --all 'bool "no" "yes" . isInfixOf "1000"'
 yes
 ```
 
 ## Configuration
-`hwk` uses a Haskell configuration file `~/.config/hwk/Hwk.hs` which provides the context for the hint evaluation of the supplied function.
+`hwk` uses a Haskell configuration file `~/.config/hwk/Hwk.hs` which provides the context for the hint evaluation of the supplied function. Hint (ghci) also checks the current directory when loading so one can also override the configuration on a directory basis.
 
-The default configuration [Hwk module](data/Hwk.hs) just sets
-the `Prelude`, `Data.List`, and `Data.Char` modules to be imported into the hint interpreter.
+The default [Hwk module](data/Hwk.hs) configuration imports
+`Prelude`, `Data.List`, `Data.Char`, and `System.FilePath`
+into the hint interpreter.
 
 The first time hwk is run it sets up `~/.config/hwk/Hwk.hs`.
 
 You can add other modules to import or define your own functions in
 `~/.config/hwk/Hwk.hs`.
 
-After a hwk version update you may wish/have to update up your Hwk.hs file to take account of new changes: a copy of the latest default Hwk.hs is also put in `~/.config/hwk/` with version suffix.
+After a hwk version update you may wish or have to update up your Hwk.hs file to take account of new changes: a copy of the latest default Hwk.hs is also put in `~/.config/hwk/` with the version suffix.
 
 ## Install
 Either use the `install.sh` script, or install by cabal-install or stack
@@ -92,23 +102,28 @@
 ## How does `hwk` work?
 
 - `hwk` use the hint library to evaluate haskell functions on standard input.
-- By default it splits the input to a list of lines: `[String] -> ToList a`
-- Use `-a` or `--all` to apply a function to all the input: `String -> Tolist a`
+- 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.
+- You can only typecheck the function or an expr with `-t`/`--typecheck`
+  or evaluate an expr with `-e`/`--eval`.
 
 ## Supported return types
 
-By default the following instances of the `ToList` class are defined:
+The following return values are supported:
 
 - `String`
 - `[String]`
 - `[[String]]`
 - `Int`
 - `[Int]`
+- `[[Int]]`
 
 ## Contribute
 
 Open an issue or pull request at https://github.com/juhp/hwk
 to report problems or make suggestions and contributions.
+Usage examples are also welcome.
 
 ## Related/alternative projects
 
diff --git a/hwk.cabal b/hwk.cabal
--- a/hwk.cabal
+++ b/hwk.cabal
@@ -1,10 +1,11 @@
 name:                hwk
-version:             0.3
-synopsis:            Simple Haskell-based awk-like tool
+version:             0.4
+synopsis:            Simple cli text processing with Haskell functions
 description:
-            A simple Haskell-based alternative to awk/sed.
-            It uses Hint to apply the function given on the commandline
-            to standard input and outputs the result.
+            A commandline tool for text processing with Haskell functions,
+            which can used in addition to awk, grep, sed, etc.
+            It applies the function supplied on the commandline using hint
+            to lines of standard input and outputs the results.
 homepage:            https://github.com/juhp/hwk
 license:             MIT
 license-file:        LICENSE
@@ -14,7 +15,7 @@
                      2020 Jens Petersen
 category:            Development
 build-type:          Simple
-data-files:          Config.hs
+data-files:          Hwk.hs
 extra-source-files:  install.sh
 extra-doc-files:     README.md
                      ChangeLog.md
