上下文:从将运行时错误转换为编译时错误的角度来看,我正在接近Haskell。我的假设是,如果可以在程序类型本身内编写业务逻辑,那么这是可能的。
我正在写一个Telegram bot,我公司的用户应该可以访问它。要实现此限制",只要有人开始与机器人聊天,它就会在表格中查找chat_id
并检查是否存在有效的oauth_token
。如果没有,系统会首先向用户发送完成Google OAuth的链接(我们公司的电子邮件托管在Google Apps for 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
|]
具有有效oauth_token
的用户将能够为Telegram bot提供一些命令,未经身份验证的用户无法提供这些命令。
现在,我试图在类型级别编写这个逻辑。在我的Haskell代码中将有一些函数能够接受作为参数的两者,经过验证和验证。未经认证的用户;而某些功能应该只接受经过身份验证的用户。
如果我继续传递相同类型的用户对象,即VLUser
无处不在,那么我必须小心检查每个函数中是否存在oauthToken
。有没有办法创建两种用户类型 - VLUser
和VLUserAuthenticated
,其中:
VLUserAuthenticated
只有oauthToken
答案 0 :(得分:8)
Phantom types to the rescue!是Bryan O' Sullivan使用phantom types实现类型级别的读/写访问的示例。
同样,对于您的用例:
data Unknown -- unknown users
data Authenticated -- verified users
newtype User a i = Id i deriving Show
重要数据构造函数Id
不向用户公开,但该模块提供了初始化和验证用户的功能:
-- initializes an un-authenticated user
newUser :: i -> User Unknown i
newUser = Id
-- authenticates a user
authUser :: (User a i) -> User Authenticated i
authUser (Id i) = Id i -- dummy implementation
然后,您可以控制类型级的访问权限,无需代码重复,没有运行时检查和而无需运行时成本:
-- open to all users
getId :: User a i -> i
getId (Id i) = i
-- only authenticated users can pass through
getId' :: User Authenticated i -> i
getId' (Id i) = i
例如,如果
\> let jim = newUser "jim"
\> let joe = authUser $ newUser "joe"
joe
是经过身份验证的用户,可以传递给任一函数:
\> getId joe
"joe"
\> getId' joe
"joe"
然而,如果您使用getId'
致电jim
,则会收到编译时错误:
\> getId jim
"jim"
\> getId' jim -- compile-time error! not run-time error!
<interactive>:28:8:
Couldn't match type ‘Unknown’ with ‘Authenticated’
Expected type: User Authenticated [Char]
Actual type: User Unknown [Char]
In the first argument of ‘getId'’, namely ‘jim’
In the expression: getId' jim