content: 
  
  This file contains the functions for resolving types and
  function/operator resolution (which is seriously crazy). See the pg
  manual chapter 10:
  
  http://www.postgresql.org/docs/8.4/interactive/typeconv.html
  
  sql server todo: want to match sql server implicit cast rules better
  when doing tsql type checking. Follows a completely different approach, possible info here:
  http://msdn.microsoft.com/en-us/library/ms187928.aspx
  http://msdn.microsoft.com/en-us/library/ms190309.aspx
  linked from here:
  http://blogs.msdn.com/b/craigfr/archive/2010/01/20/more-on-implicit-conversions.aspx
  
  
  TODO: rewrite
  Wrappers to zap:
  
  tcAppLike
  matchApp
  matchAppExtra
  resolveResultSetType
  resolveResultSetTypeExtra
  findcallmatch
  resolveresultsettype
  checkassignments
  
  We should have one function for each of the resolvers (3 just like
  postgres) all the nullability, precision, dialect and special hacks
  should all be in one place.
  
  > {-# LANGUAGE OverloadedStrings, TupleSections, MultiWayIf,FlexibleInstances,ScopedTypeVariables #-}
  > module Database.HsSqlPpp.Internals.TypeChecking.TypeConversion.TypeConversion
  >     (matchApp
  >     ,matchAppExtra
  >     ,tcAppLike
  >     ,resolveResultSetType
  >     ,resolveResultSetTypeExtra
  >     ,checkAssignmentValid
  >     ,MatchAppLiteralList -- (..)
  >     ) where
  >
  > import Data.Maybe
  > import Data.List
  > import Data.Either
  > import Data.Char
  >
  > import Database.HsSqlPpp.Internals.TypesInternal
  > --import Database.HsSqlPpp.Internals.Catalog.CatalogInternal
  > import Database.HsSqlPpp.Internals.Catalog.CatalogTypes
  > import Database.HsSqlPpp.Internals.Utils
  > import Control.Monad
  > import Control.Applicative
  > import Control.Arrow
  
  > import Database.HsSqlPpp.Internals.TypeChecking.TypeConversion.OldTypeConversion
  > import Database.HsSqlPpp.Internals.Dialect
  > import qualified Database.HsSqlPpp.Internals.TypeChecking.TypeConversion.SqlTypeConversion as TSQL
  > import Data.Text ()
  > import qualified Data.Text as T
  > import Text.Printf
  > --import Debug.Trace
  
  ******************************************************************
  
  matchApp: takes the function name and argument types, and returns the
  matching operator/function
  
  This needs a lot more tests
  
  > type MatchAppLiteralList = [Maybe Int]
  
  > matchApp :: Dialect
  >          -> Catalog
  >          -> [NameComponent]
  >          -> [Type]
  >          -> Either [TypeError] ([Type],Type)
  > matchApp d cat nmcs =
  >     ambiguityResolver $ matchApp' d nmcs
  >   where
  >     -- matchApp' a b c | trace (show (a,b,c)) False = undefined
  >     -- hack in support for sql server datediff function
  >     -- need to think of a better way to handle this when
  >     -- have a better idea of all the weird syntax used in
  >     -- tsql
  >     -- this is a todo since currently the sql server dialect uses
  >     -- postgresql type names, and this is about to be fixed
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer})
  >                   [Nmc dd] [_
  >                                  ,ScalarType "date"
  >                                  ,ScalarType "date"]
  >       | map toLower dd == "datediff" =
  >       -- check there are 3 args
  >       -- first is identifier from list
  >       -- other two are date types
  >       Right ([ScalarType "int4",ScalarType "date",ScalarType "date"], ScalarType "int4")
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer}) [Nmc dd] [_,ScalarType "date"]
  >       | map toLower dd == "datepart" =
  >       Right ([ScalarType "int4",ScalarType "date"], ScalarType "int4")
  
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer}) [Nmc dd] [_,ScalarType "timestamp"]
  >       | map toLower dd == "datepart" =
  >       Right ([ScalarType "int4",(ScalarType "timestamp")], ScalarType "int4")
  
  
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer}) [Nmc dd] [_,_,ScalarType "date"]
  >       | map toLower dd == "dateadd" =
  >       Right ([ScalarType "int4",ScalarType "int4",ScalarType "date"], ScalarType "date")
  
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer}) [Nmc dd] [_,_,ScalarType "timestamp"]
  >       | map toLower dd == "dateadd" =
  >       Right ([ScalarType "int4",ScalarType "int4",ScalarType "timestamp"], ScalarType "timestamp")
  
  double hack: support oracle decode when in tsql mode:
  
  >     matchApp' (Dialect {diSyntaxFlavour = SqlServer}) [Nmc dd] as
  >       | map toLower dd == "decode" =
  
  decode is just syntax for simple case statement:
  demand at least 3 arguments
  get the type of the first argument: this is the test target
  
  >       case as of
  >         (tt:as'@(_:_:_)) -> do
  
  for each pair of arguments following: check the first
  one can be compared to the test target
  
  collect all the second types
  if there is a single trailing argument this is the else
  
  >             let checkBranches [] acc = return $ reverse acc
  >                 checkBranches [els] acc = return $ reverse (els:acc)
  >                 checkBranches (w:t:xs) acc = do
  >                   _ <- matchApp' d [Nmc "="] [tt,w]
  >                   checkBranches xs (t:acc)
  >             sndTypes <- checkBranches as' []
  
  check the seconds types + the else for type compatilibility
  return this type
  todo: add the implicit casting where needed
  
  >             (as,) <$> resolveResultSetType cat sndTypes
  
  
  >         _ -> Left [NoMatchingOperator (T.pack dd) as]
  
  
  
  >     matchApp' d' nmcs' pts = {-trace ("matchapp: " ++ show (d,nmcs,pts)) $ -} do
  >       (_,ps,r,_) <- case d' of
  >                       (Dialect {diSyntaxFlavour = SqlServer}) -> TSQL.findCallMatch d' cat nm pts
  >                       _ -> findCallMatch d' cat nm pts
  >       return (ps,r)
  >       where
  >         nm = case last nmcs' of
  >                Nmc n -> T.pack $ map toLower n
  >                QNmc n -> T.pack n
  >                AntiNameComponent _ -> -- todo: use left instead of error
  >                  error "tried to find function matching an antinamecomponent"
  
  hack to support literal arguments to overloaded functions
    currently, the problem arises only for date/datetime arguments
    the solution reflects this
  for long argument lists with several literals, there can be a lot of variants generated, but
    this shouldn't slow down the execution because lazyness composes well with the functions used
  
  > ambiguityResolver:: ([Type] -> Either [TypeError] ([Type],Type))
  >                     -> [Type] -> Either [TypeError] ([Type],Type)
  > ambiguityResolver f ts =  let rs :: [Either [TypeError] ([Type], Type)]
  >                               rs = map f $ expandList variants ts
  >                           -- this is needed in order to preserve the original error
  >                           --  in case when all the attempts fail
  >                           in case rights rs of
  >                                  x:_ -> Right x
  >                                  [] -> head rs
  >   where
  >     variants t = case t of
  >         UnknownType -> [t, ScalarType "timestamp"]
  >         _ -> [t]
  >     -- similar to handling of superaggregates
  >     -- ToDo: move to a general library
  >     expandList:: (a -> [a]) -> [a] -> [[a]]
  >     expandList f' = foldr (liftM2 (:) . f') [[]]
  
  ------------- precision and nullability of function application --------------
  
  uses matchApp for inferring basic types
  
  > matchAppExtra :: Dialect
  >                 -> Catalog
  >                 -> [NameComponent]
  >                 -> MatchAppLiteralList
  >                 -> [TypeExtra]
  >                 -> Either [TypeError] ([TypeExtra],TypeExtra)
  > matchAppExtra dialect cat nmcs lits tes = {-trace ("mae" ++ show (nmcs,tes)) $ -} do
  >     (ts',t') <- matchApp dialect cat nmcs $ map teType tes
  >     tes' <- joinArgsExtra appName tes $ zipWith addArgExtra tes ts'
  >     return (tes', addResultExtra appName tes' t' lits)
  >   where
  >     addArgExtra te t = te {teType = t}
  >     appName = case nmcs of
  >       [Nmc dd] -> map toLower dd
  >       _ -> ""
  
  precision and nullability of the result
  
  > addResultExtra:: String -> [TypeExtra] -> Type -> MatchAppLiteralList -> TypeExtra
  > addResultExtra appName tes t lits = checkPrecisionRelevance . checkResultPrecisionClass tesr
  >                                 $ TypeExtra t jp js jn
  >   where
  >     jp = if
  >       | appName == "||" -> Just $ sum $ mapMaybe tePrecision tesr
  >       | appName == "substring" ->
  >            -- Substring is an interesting case. If we have both the
  >            -- start and length as literals, we can figure out the resulting precision
  >            -- Otherwise, treat as before with joinPrecision
  >            let totalLen = joinPrecision $ map tePrecision tesr
  >            in case (map teType tesr) of
  >              [ScalarType "nvarchar"] -> totalLen
  >              _ ->
  >                  case lits of
  >                    (Nothing:(Just startPos):(Just len):_) -> do
  >                       totalLen' <- totalLen
  >                       return $ if startPos + len > totalLen' then totalLen' - startPos else len
  >                    _ -> totalLen
  
  >         -- precision of the result is unknown
  >       | appName `elem` ["replace"] -- is actually known for 2-argument "replace"
  >         -> Nothing
  >       | otherwise
  >         -> joinPrecision $ map tePrecision tesr
  >     js = joinScale $ map teScale tesr
  >     jn = if
  >       | appName `elem`
  >           ( ["isnotnull","isdate","isnumeric"]
  >               -- standard "is null" expression
  >             ++ ["isnull" | length tes == 1]
  >               -- currently, aggregate functions are handled as scalar functions
  >             ++ ["count","count_big"])
  >         -> False
  >       | appName `elem`
  >           ( ["coalesce","greatest","least"]
  >               -- 2-argument function "isnull" of SqlServer
  >               -- ImplicitCastToDo: isnull has quite complex cast rules,
  >               --    not really reflected here and in the cast of arguments
  >             ++ ["isnull" | length tes == 2]
  >               -- nullability of corresponding SqlServer function "charindex"
  >               --  may or may not differ, depending on database compatibility level
  >               -- I implement the level above 70, so it goes to the default case
  >             ++ ["strpos","position"])
  >         -> all teNullable tesr
  >         -- can produce null independently on the nullability of the arguments
  >         -- ImplicitCastToDo: check again: doesn't it depend on the presence of "else" part
  >       | appName `elem` ["case","decode","nullif","replicate"]
  >         -> True
  >         -- the default
  >       | appName `elem` ["substr","substring"] -> any teNullable tes
  >       | otherwise -> joinNullability $ map teNullable tesr
  >     -- arguments that participate in the inference of the result type
  >     tesr = case appName of
  >       "decode" -> caseResultTypes tes
  >           -- only the first argument influences precision and nullability
  >       _ | appName `elem` ["nullif","substr","substring","left","right","ltrim","rtrim","replicate","translate","like","notlike","rlike"]
  >           -> take 1 tes
  >           -- the first two arguments influence
  >         | appName `elem` ["charindex"]
  >           -> take 2 tes
  >           -- the first argument doesn't influence
  >         | appName `elem` ["datepart","datediff"]
  >           -> drop 1 tes
  >           -- the first two arguments don't influence
  >         | appName `elem` ["dateadd"]
  >           -> drop 2 tes
  >       -- the default case
  >         | otherwise -> tes
  >     -- tail is safe here because matchApp did all the checks
  >     caseResultTypes tes' = caseResultTypes' (tail tes') []
  >       where
  >         caseResultTypes' [] acc = acc
  >         caseResultTypes' [els] acc = els:acc
  >         caseResultTypes' (_:t':xs) acc = caseResultTypes' xs (t':acc)
  
  ------------- cast of arguments --------------
  
  Bring relevant arguments of a function to common precision and nullability.
  The meaning of "relevant" is complicated:
    - the list of arguments is split into partitions;
    - precision and nullability is partition-wise joined;
    - the results are broadcast back to the arguments that constituted partitions.
  The algorithm follows this outline.
  Examples:
    - for binary operators, like "=", both arguments constitute a single partition;
    - for some functions, like "substr", each argument belongs to its own partition;
    - arguments of "case" and "decode" form two partitions which, except for the analyzed value
      and the 'else' argument, are intervened with one another.
  Actually, partitions for precision and partitions for nullability can be different.
  Example:
    "||": both arguments must be brought to common nullability, but remain with same precision.
  Additionaly:
    Before splitting onto partitions, check each argument for:
      - precision class of the original (before matchApp) argument;
      - precision relevance.
    After splitting onto partitions, check each precision partition:
      - all arguments must have same precision class (return an error if they don't).
  
  What the is this function doing? What is it for?
  
  > joinArgsExtra:: String -> [TypeExtra] -> [TypeExtra] -> Either [TypeError] [TypeExtra]
  > joinArgsExtra "!odbc-left" _t0 t1 = Right t1
  > joinArgsExtra "!odbc-timestampdiff" _t0 t1 = Right t1
  > joinArgsExtra "!odbc-timestampadd" _t0 t1 = Right t1
  
  > joinArgsExtra appName tes0 tes1
  >     = liftM (uncurry $ zipWith3 combine tes) $ uncurry (liftM2 (,))
  >       $ (joinDim joinPrec partitionPrec &&& joinDim joinNull partitionNull) tes
  >   where
  >     -- checks and adjustments before partitioning
  >     tes = map checkPrecisionRelevance
  >           $ zipWith adjust tes0
  >           $ zipWith checkPrecisionClass tes0 tes1
  >       where
  >         adjust te0 te1 = te1{tePrecision = head $ adjustStringCastPrec (teType te1) [te0]}
  >     -- checks after partitioning
  >     checkPartition ptes = if length (nub ppcs) > 1
  >         then Left [InternalError $ printf "implicit cast: arguments of '%s' that belong to same partition are of different precision classes: %s -> %s" appName (show ptes) (show ppcs)]
  >         else return ptes
  >       where
  >         ppcs = map (precisionClass . teType) ptes
  >     -- the algorithm for a single partitioning dimension
  >     joinDim:: ([TypeExtra] -> [TypeExtra])
  >               -> ([TypeExtra] -> Either [TypeError]
  >                                         ([[TypeExtra]] -> [TypeExtra], [[TypeExtra]]))
  >               -> [TypeExtra] -> Either [TypeError] [TypeExtra]
  >     joinDim join' partitionArgs'
  >         = liftM (uncurry ($) . second (map join')) . partitionArgs'
  >     -- combine results for precision and nullability
  >     combine te tePrec teNull = te {
  >       tePrecision = tePrecision tePrec,
  >       teScale = teScale tePrec,
  >       teNullable = teNullable teNull
  >     }
  >     -- joins of precision and nullability partitions
  >     joinPrec tes' = map promote tes'
  >       where
  >         promote (TypeExtra t p s n) = TypeExtra t (p' `mplus` p) (s' `mplus` s) n
  >         p' = joinPrecision $ map tePrecision tes
  >         s' = joinScale $ map teScale tes
  >     joinNull tes' = map promote tes'
  >       where
  >         promote (TypeExtra t p s n) = TypeExtra t p s (n' || n)
  >         n' = joinNullability $ map teNullable tes
  >     -- the partitioning functions return partitions paired with a function
  >     --    that puts them back into their places
  >     -- because, in many cases, partitions for precision and for nullability are the same,
  >     --    the partitioning code for such cases is factored out
  >     partitionArgs:: [a] -> ([[a]] -> [a], [[a]])
  >     partitionArgs as = case () of
  >             -- functions whose arguments are independent
  >             --  instead of splitting into partitions, just return the original list
  >         _ | appName `elem`
  >               ( ["datepart","dateadd"]
  >                 ++ ["substr","substring","left","right","ltrim","rtrim"]
  >                 ++ ["replicate","like","notlike","rlike"]
  >                 ++ ["strpos","position","replace"]
  >                     -- Oracle joins the datatypes (needed for the comparison)
  >                 ++ ["nullif"]
  >                 -- SQream specific regex functions
  >                 ++ ["regexp_substr","regexp_count","regexp_instr"]
  >                 ++ ["string_agg"]
  >               )
  >             -> (const as, [])
  >             -- first argument is special, the rest are processed together
  >           | appName `elem` ["datediff"]
  >             -> (concat, pairToList $ splitAt 1 as)
  >           | appName `elem` ["decode"]
  >             ->  let (ws,ts) = decomposeDecodeTail (tail as) ([],[])
  >                 in (composeDecodePartitions, [head as :ws, ts])
  >             -- the default is to return a single partition
  >           | otherwise -> (concat, [as])
  >     partitionPrec as = secondM (mapM checkPartition) $ case () of
  >             -- independent arguments
  >         _ | appName `elem` ["||","concat","translate","charindex"]
  >             -> (const as, [])
  >             -- single partition
  >           | appName `elem`
  >               ( ["coalesce","greatest","least"]
  >                     -- ImplicitCastToDo: think how to handle this properly
  >                 ++ ["isnull" | length as == 2]
  >               )
  >             -> (concat, [as])
  >           | otherwise -> partitionArgs as
  >     partitionNull as = return $ case () of
  >         _ | appName `elem`
  >               ( ["coalesce","greatest","least"]
  >                 ++ ["isnull" | length as == 2]
  >               )
  >             -> (const as, [])
  >           | appName `elem` ["charindex"]
  >             -> (concat, pairToList $ splitAt 2 as)
  >           | otherwise -> partitionArgs as
  >     -- utility
  >     pairToList (x,y) = [x,y]
  >     decomposeDecodeTail [] acc = (reverse***reverse) acc
  >     decomposeDecodeTail [els] acc = (reverse.(els:)***reverse) acc
  >     decomposeDecodeTail (w:t:xs) acc = decomposeDecodeTail xs $ ((w:)***(t:)) acc
  >     composeDecodePartitions [t:ts,ws] = t : concat (transpose [ts,ws])
  >     -- redundant
  >     composeDecodePartitions xs = concat xs
  
  ------------- precision class --------------
  
  This is a small library for checking whether inference of precision does make sense.
  It is used both in inference of precision of arguments and result of a function.
  
  It is theoretically possible that types belong to different precision classes,
      but inference of precision still makes sense (consider, for instance,
      conversion between string and decimal).
    Such cases must be handled specially.
  
  > data PrecisionClass = String | Number | FlexiblePrecisionClass
  >       deriving (Eq,Show)
  >
  > precisionClass:: Type -> Maybe PrecisionClass
  > precisionClass (ScalarType tn)
  >   | tn `elem` ["text","varchar","nvarchar","char"] = Just String
  >   | tn `elem` ["int1","int2","int4","int8","float4","float8","numeric"] = Just Number
  >   | otherwise = Nothing
  > precisionClass UnknownType = Just FlexiblePrecisionClass
  > precisionClass _ = Nothing
  
  Do original and new type have compatible precision classes?
  Note: this function is not commutative.
  
  > infix 4 .~>.
  > (.~>.):: TypeExtra -> TypeExtra -> Bool
  > t0 .~>. t = Just FlexiblePrecisionClass `elem` [pc0,pc] || pc0 == pc
  >   where
  >     [pc0,pc] = map (precisionClass . teType) [t0,t]
  
  retreat to default when original and new type are incompatible
  
  > checkPrecisionClass:: TypeExtra -> TypeExtra -> TypeExtra
  > checkPrecisionClass t0 t = if t0 .~>. t then t else t{tePrecision = Nothing, teScale = Nothing}
  
  check precision class of result against precision classes of arguments
  
  > checkResultPrecisionClass:: [TypeExtra] -> TypeExtra -> TypeExtra
  > checkResultPrecisionClass tes t
  >   = if and $ map (.~>. t) tes then t else t{tePrecision = Nothing, teScale = Nothing}
  
  check whether precision/scale is relevant for a type (consider "round").
  
  > checkPrecisionRelevance:: TypeExtra -> TypeExtra
  > checkPrecisionRelevance te = if
  >   | Just String <- pc
  >     -> te{teScale = Nothing}
  >   | Just FlexiblePrecisionClass <- pc
  >     -> te
  >   | ScalarType tn <- t, tn == "numeric"
  >     -> te
  >   | otherwise
  >     -> te{tePrecision = Nothing, teScale = Nothing}
  >   where
  >     t = teType te
  >     pc = precisionClass t
  
  
  The purpose of the tcapplike wrapper is to partially fix up the
  keyword 'enums' in some of the ms style functions (like
  datepart). This is a horrible hack and will be fixed.
  
  > tcAppLike :: Dialect -> Catalog -> [NameComponent]
  >            -> [Maybe Int] -> [Maybe TypeExtra]
  >            -> Either [TypeError] ([TypeExtra],TypeExtra)
  
  > tcAppLike d cat anm@[Nmc dd] lits [_,a0,a1]
  >     | map toLower dd == "datediff" = do
  >   -- dodgy hack for datediff
  >   tys <- mapM (maybe (Left []) Right) [a0,a1]
  >   typeInt <- ScalarType <$> maybe (Left []) Right
  >              (ansiTypeNameToDialect d "int")
  >   let --Name _ ns = anm
  >   (ats,rt) <- matchAppExtra d cat anm lits (mkTypeExtraNN typeInt : tys)
  >   return (ats,rt)
  > tcAppLike d cat anm@[Nmc dd] lits [_,a0]
  >     | map toLower dd == "datepart" = do
  >   tys <- mapM (maybe (Left []) Right) [a0]
  >   typeInt <- ScalarType <$> maybe (Left []) Right
  >              (ansiTypeNameToDialect d "int")
  >   (ats,rt) <- matchAppExtra d cat anm lits (mkTypeExtraNN typeInt : tys)
  >   return (ats,rt)
  > tcAppLike d cat anm@[Nmc dd] lits [_,a0,a1]
  >     | map toLower dd == "dateadd" = do
  >   tys <- mapM (maybe (Left []) Right) [a0,a1]
  >   typeInt <- ScalarType <$> maybe (Left []) Right
  >              (ansiTypeNameToDialect d "int")
  >   (ats,rt) <- matchAppExtra d cat anm lits (mkTypeExtraNN typeInt : tys)
  >   return (ats,rt)
  
  > --tcAppLike d cat anm@(Name _ [Nmc dd]) a b
  > --    |  trace ("xz: " ++ show (a,b)) False = undefined
  
  > tcAppLike d cat anm@[Nmc dd] _ ts
  >     | map toLower dd == "!odbc-convert" = do
  >                  (tys :: [TypeExtra]) <- mapM (maybe (Left []) Right) ts
  >                  --let Name _ ns = anm
  >                  (ats,rt) <- matchAppExtra d cat anm [] tys
  >                  return (ats,rt)
  
  > tcAppLike d cat anm@[Nmc dd] _ [_,a0,a1]
  >     | map toLower dd `elem` ["!odbc-timestampadd","!odbc-timestampdiff"] = do
  >   tys <- mapM (maybe (Left []) Right) [a0,a1]
  >   --let Name _ ns = anm
  >   typeInt <- ScalarType <$> maybe (Left []) Right
  >              (ansiTypeNameToDialect d "int")
  >   (ats,rt) <- matchAppExtra d cat anm [] (mkTypeExtraNN typeInt : tys)
  >   return (ats,rt)
  
  > tcAppLike d cat anm lits teArgs = do
  >   -- get the types of the arguments
  >   -- then lookup in TypeConversion.matchAppExtra
  >   tys <- mapM (maybe (Left []) Right) teArgs
  >   (ats,rt) <- matchAppExtra d cat anm lits tys
  >   return (ats,rt)
  
  
  ******************************************************************
  
   findCallMatch :: Catalog -> String -> [Type] ->  Either [TypeError] OperatorPrototype
   findCallMatch cat fnName' argsType =
  
  code interspersed with text cut and pasted from postgresql manual
  10.3. Functions
  
  Function Type Resolution
  
  Select the functions to be considered from the pg_proc system
  catalog. If a non-schema-qualified function name was used, the
  functions considered are those with the matching name and argument
  count that are visible in the current search path (see Section
  5.7.3). If a qualified function name was given, only functions in the
  specified schema are considered.
  
  [HsSqlPpp doesn't support schema stuff yet, so just get a list of all
  the functions with a matching name]
  
  >   {-let matchingNames = catGetOpsMatchingName cat nmcs
  >       exactMatches = filter (\(_,ts,_,_) -> ts == pts) matchingNames
  >   case exactMatches of
  >     [(_,tys,rt,_)] -> return (tys, rt)
  >     [] -> error $ "no matching fn: " ++ show nmcs
  >                   ++ "(" ++ intercalate "," (map show pts) ++ ")"
  >     _xs -> error "ambiguous"-}
  
  
  
  If the search path finds multiple functions of identical argument
  types, only the one appearing earliest in the path is
  considered. Functions of different argument types are considered on an
  equal footing regardless of search path position.
  
  If a function is declared with a VARIADIC array parameter, and the
  call does not use the VARIADIC keyword, then the function is treated
  as if the array parameter were replaced by one or more occurrences of
  its element type, as needed to match the call. After such expansion
  the function might have effective argument types identical to some
  non-variadic function. In that case the function appearing earlier in
  the search path is used, or if the two functions are in the same
  schema, the non-variadic one is preferred.
  
  Functions that have default values for parameters are considered to
  match any call that omits zero or more of the defaultable parameter
  positions. If more than one such function matches a call, the one
  appearing earliest in the search path is used. If there are two or
  more such functions in the same schema with identical parameter types
  in the non-defaulted positions (which is possible if they have
  different sets of defaultable parameters), the system will not be able
  to determine which to prefer, and so an "ambiguous function call"
  error will result if no better match to the call can be found.
  
  Check for a function accepting exactly the input argument types. If
  one exists (there can be only one exact match in the set of functions
  considered), use it. (Cases involving unknown will never find a match
  at this step.)
  
  If no exact match is found, see if the function call appears to be a
  special type conversion request. This happens if the function call has
  just one argument and the function name is the same as the (internal)
  name of some data type. Furthermore, the function argument must be
  either an unknown-type literal, or a type that is binary-coercible to
  the named data type, or a type that could be converted to the named
  data type by applying that type's I/O functions (that is, the
  conversion is either to or from one of the standard string
  types). When these conditions are met, the function call is treated as
  a form of CAST specification. [1]
  
  Look for the best match.
  
  Discard candidate functions for which the input types do not match and
  cannot be converted (using an implicit conversion) to match. unknown
  literals are assumed to be convertible to anything for this
  purpose. If only one candidate remains, use it; else continue to the
  next step.
  
  Run through all candidates and keep those with the most exact matches
  on input types. (Domains are considered the same as their base type
  for this purpose.) Keep all candidates if none have exact matches. If
  only one candidate remains, use it; else continue to the next step.
  
  Run through all candidates and keep those that accept preferred types
  (of the input data type's type category) at the most positions where
  type conversion will be required. Keep all candidates if none accept
  preferred types. If only one candidate remains, use it; else continue
  to the next step.
  
  If any input arguments are unknown, check the type categories accepted
  at those argument positions by the remaining candidates. At each
  position, select the string category if any candidate accepts that
  category. (This bias towards string is appropriate since an
  unknown-type literal looks like a string.) Otherwise, if all the
  remaining candidates accept the same type category, select that
  category; otherwise fail because the correct choice cannot be deduced
  without more clues. Now discard candidates that do not accept the
  selected type category. Furthermore, if any candidate accepts a
  preferred type in that category, discard candidates that accept
  non-preferred types for that argument.
  
  If only one candidate remains, use it. If no candidate or more than
  one candidate remains, then fail.
  
  
  
  
  exact match
  binop1unknownmatch
  polymorphic matches
  reachable
  mostexactmatches
  filteredforpreferred
  unknownmatchesbycat
  
  
  
  TODO: do a log monad, which can record the tests and then return this
  process along with the resolved function
  
  
  --------------------------------
  todo:
  
  > {-resolveResultSetType :: Catalog -> [Type] -> Either [TypeError] Type
  > resolveResultSetType _cat [] = error "resolveResultSetType: empty type set"
  > resolveResultSetType _cat (t:ts) =
  >   if all (==t) ts
  >   then Right t
  >   else Left [IncompatibleTypeSet (t:ts)]-}
  
  todo:
  
  assignmentCheck
  