Maybe or not Maybe - that is the question:
whether 'tis nobler in the mind to suffer
the slings and arrows of outrageous fortune,
or to take arms against a sea of troubles,
and by opposing end them

(Hamlet, III, i - almost)

maybe-программирование

это если нам пофиг, если что не так, но мы все-таки знаем, что бывает что-то не так и хотим таким разом не попортить наши данные и не выдать лажу на терминал/принтер

чтобы справиться с нетотальностью функций ("что-то пошло не так, кажется сейчас будет катастрофа" ©) у приплюснутых есть несколько способов:

хаскельнутым хватает одного Maybe

давайте озадачимся. пусть БД содержит следующие таблицы:

users, c полями : (int) uid (string) name
phones, с полями : (string) name (string) number
codes, с полями : (int) code (string) country

пусть нам необходимо выбрать из БД страну обитания персонажа (country), используя в качестве входных данных его личный номер (uid)

для простоты предположим, что телефонный код страны - это первые три знака телефонного номера number


приплюснутые

чтобы не таскать код базы из примера в пример, поместим его в отдельный файл simpleDB.cpp:

// file simpleDB.cpp

#include <iostream>
#include <string>
#include <map>

using namespace std ;

map <int ,    string> users ;
map <string , string> codes ;
map <string , string> phones ;

void
init (void)
{
  users [1] = "Neo" ;
  users [2] = "Cipher" ;
  users [3] = "Morpheus" ;  
  users [4] = "Trinity" ;

  phones ["Cipher"]   = "" ;
  phones ["Morpheus"] = "12" ;
  phones ["Neo"]      = "123456" ;
  phones ["Trinity"]  = "456789" ; 

  codes ["123"] = "USA" ; 
  codes ["456"] = "RUS" ;

  cout<< "db initiated" <<endl ;
}

string 
findUser (int userid)
{
  map <int, string> :: iterator k = users.find (userid) ;
  if (k != users.end()) return  k->second ;  else return "" ; 
}

string 
getPhone (string user)
{
  map <string, string> :: iterator k = phones.find (user) ;
  if (k != phones.end()) return k->second ; else return "" ; 
}

string 
getCode (string phone) 
{
  string code = phone.substr (0, 3) ;
  if (code.length () == 3) return code ; else return "" ;
}

string 
findByCode (string code) 
{
  map <string, string> :: iterator k = codes.find (code) ;
  if (k != codes.end()) return k->second ; else return "" ;
}

главную функцию программы тоже менять не будем. поместим следующий код в файл main.cpp

//   file main.cpp

#include <iostream>
#include <string>

using namespace std ;

extern void init (void) ;
extern string countryName (int) ;

int
main (void)
{
  init () ;

  for ( ; ; )
  {
    int userid ;

    cout<< "userID : " ; cin>> userid ;
    cout<< countryName (userid) <<endl ;
  } ;

  return 0 ;
}
соответсвенно парадигме будем кодить функцию string countryName (int), помещая сей продукт в файл work.cpp

ну, приступим ...


NULL

// file work.cpp

#include <string>

using namespace std ;

string 
countryName (int d)
{
  string ans ;

  extern string findUser   (int) ;
  extern string getPhone   (string) ;
  extern string getCode    (string) ;
  extern string findByCode (string) ;

  if (d != NULL)
  {
    ans =  (ans = findUser     (d)) != "" ? ans : NULL ;
    ans =  (ans = getPhone   (ans)) != "" ? ans : NULL ; 
    ans =  (ans = getCode    (ans)) != "" ? ans : NULL ;
    return (ans = findByCode (ans)) != "" ? ans : NULL ;
  }
  else return NULL ;
}

NullObject

// file work.cpp

#include <string>

using namespace std ;

string
countryName (int d)
{
  string ans, NullUser, NullPhone, NullCode, NullCountry ;

  NullUser = NullPhone = NullCode = NullCountry = "" ;

  extern string findUser   (int) ;
  extern string getPhone   (string) ;
  extern string getCode    (string) ;
  extern string findByCode (string) ;
  
  (ans = findUser     (d)) != "" ? ans : NullUser ;
  (ans = getPhone   (ans)) != "" ? ans : NullPhone ; 
  (ans = getCode    (ans)) != "" ? ans : NullCode ; 
  (ans = findByCode (ans)) != "" ? ans : NullCountry ;
  return ans ;
}

exceptions

// file work.cpp

#include <string>
#include <iostream>

using namespace std ;

struct NFE {NFE (string s) { cerr<< s <<endl ; }} ;

string 
countryName (int d)
{
  string ans ;

  extern string findUser   (int) ;
  extern string getPhone   (string) ;
  extern string getCode    (string) ;
  extern string findByCode (string) ;

  try { (ans = findUser (d)) != "" ? ans : throw NFE ("user not found!") ; }
  catch (NFE) { return "exeption has raisen!" ; }

  try { (ans = getPhone (ans)) != "" ? ans : throw NFE ("phone not found!") ; }
  catch (NFE) { return "exeption has raisen!" ; }

  try { (ans = getCode (ans)) != "" ? ans : throw NFE ("code not found!") ; }
  catch (NFE) { return "exeption has raisen!" ; }

  try { (ans = findByCode (ans)) != "" ? ans : throw NFE ("country not found!") ; }
  catch (NFE) { return "exeption has raisen!" ; }

  return ans ;
}

Option

// file work.cpp

#include <string>

using namespace std ;

template <class T> 
struct Option
{
  T    x ;
  bool v ;

  Option (const T& x) : x (x), v (true) { }
  Option () : v (false) { }
} ;

template <class T>
Option<T> Some (const T& x) { return Option<T> (x) ; }

template <class T>
Option<T> None () { return Option<T> () ; }

string 
countryName (int d)
{
  string         y ;
  Option<string> u, p, c, f ;

  extern string findUser   (int) ;
  extern string getPhone   (string) ;
  extern string getCode    (string) ;
  extern string findByCode (string) ;

  u = (y = findUser     (d)) != "" ? Some<string> (y) : None<string> () ;
  p = (y = getPhone   (u.x)) != "" ? Some<string> (y) : None<string> () ;
  c = (y = getCode    (p.x)) != "" ? Some<string> (y) : None<string> () ; 
  f = (y = findByCode (c.x)) != "" ? Some<string> (y) : None<string> () ; 

  if (u.v && p.v && c.v && f.v) return f.x ;
  else return "not found" ;
}

хаскельнутые

сначала - БД:

-- file DBC.hs

module DBC (findUser, getPhone, getCode, findByCode) where

data User  = U { uname   :: String, uid   :: Integer } deriving (Show)
data Phone = P { number  :: String, pname :: String  } deriving (Show)
data Codes = C { country :: String, code  :: String  } deriving (Show)

dbusers = [
    U {uname = "Morpheus", uid = 3},
    U {uname = "Trinity",  uid = 4},
    U {uname = "Cipher",   uid = 2},
    U {uname = "Neo",      uid = 1}
  ]

dbphones = [
    P {number = "123456", pname = "Neo"},
    P {number = "456789", pname = "Trinity"},
    P {number = "12",     pname = "Morpheus"},
    P {number = "",       pname = "Cipher"}
  ]

dbcodes = [
    C {country = "USA", code = "123"},
    C {country = "RUS", code = "456"}
  ]


-- interface 

findUser :: Integer -> Maybe String
findUser = \n -> case filter (\x -> uid x == n) dbusers of
  []  -> Nothing
  [x] -> Just $ uname x 

getPhone :: String -> Maybe String 
getPhone = \s -> case filter (\x -> pname x == s) dbphones of 
  []  -> Nothing
  [x] -> Just $ number x

getCode :: String -> Maybe String 
getCode = \s -> if (length s) < 3 then Nothing else Just (take 3 s) 

findByCode :: String -> Maybe String
findByCode = \s -> case filter (\x -> code x == s) dbcodes of 
  []  -> Nothing
  [x] -> Just $ country x 

теперь - собственно сам код нетотальной функции:

-- file main.hs

import DBC

main :: IO (Maybe String)
main = readLn >>= print . countryName >> main

countryName :: Integer -> Maybe String
countryName = \uid -> findUser uid >>= getPhone >>= getCode >>= findByCode

это - все. "почувствуйте разницу!" ©