haskell - Codifying presence/absence of authentication at type level -
context: i'm approaching haskell standpoint of converting runtime errors compile-time errors. hypothesis possible if 1 can codify business logic within program's types itself.
i'm writing telegram bot, should accessible users within company. achieve "restriction", whenever starts chatting bot chat_id in table , check if valid oauth_token exists. if not, user first sent link complete google oauth (our company's email hosted on google apps business).
share [mkpersist sqlsettings, mkmigrate "migrateall"] [persistlowercase| vluser email string chatid integer tgramuserid integer tgramfirstname string tgramlastname string maybe tgramusername string maybe oauthtoken string maybe deriving show |] users valid oauth_token able give telegram bot commands, unauthenticated users should not able give.
now, i'm trying codify logic @ type level itself. there functions in haskell code have ability accept, arguments, both, authenticated & unauthenticated users; while functions should accept authenticated users.
if keep passing user objects of same type, i.e. vluser everywhere, have careful check presence of oauthtoken in every function. there way create 2 user types - vluser , vluserauthenticated where:
- both map same underlying table
- a
vluserauthenticatedcan instantiated if hasoauthtoken
phantom types rescue! bryan o'sullivan example of implementing read-only vs read/write access at type level using phantom types.
similarly, use-case:
data unknown -- unknown users data authenticated -- verified users newtype user = id deriving show it important data constructor id not exposed user, module provides functions initialize , authenticate users:
-- initializes un-authenticated user newuser :: -> user unknown newuser = id -- authenticates user authuser :: (user i) -> user authenticated authuser (id i) = id -- dummy implementation then, may control access @ type level without code duplication, without run-time checks , without run-time cost:
-- open users getid :: user -> getid (id i) = -- authenticated users can pass through getid' :: user authenticated -> getid' (id i) = for example, if
\> let jim = newuser "jim" \> let joe = authuser $ newuser "joe" joe authenticated user , can passed either function:
\> getid joe "joe" \> getid' joe "joe" whereas, compile-time error if call getid' jim:
\> getid jim "jim" \> getid' jim -- compile-time error! not run-time error! <interactive>:28:8: couldn't match type ‘unknown’ ‘authenticated’ expected type: user authenticated [char] actual type: user unknown [char] in first argument of ‘getid'’, namely ‘jim’ in expression: getid' jim
Comments
Post a Comment