使用提升的数据构造函数作为幻像参数
Using a promoted data constructor as a phantom parameter
在 Maguire 的 Thinking with Types 中,p. 29,有一个如何使用提升的数据构造函数作为幻像参数的示例。这是我根据书中的例子写的一个模块。
{-# LANGUAGE DataKinds #-}
module Main where
import Data.Maybe
import Data.Proxy
-- | The only purpose of this constructor is to give us access to its
-- promoted data constructors.
data UserType = DummyUser | Admin
-- | Give some users an administration token.
data User = User
{ userAdminToken :: Maybe (Proxy 'Admin),
name :: String
}
doSensitiveThings :: Proxy 'Admin -> IO ()
doSensitiveThings _ = putStrLn "you did something sensitive"
trustedUser :: User
trustedUser = User (Just (Proxy :: Proxy Admin)) "Trust me"
main = do
doSensitiveThings (fromJust . userAdminToken $ trustedUser)
我知道这使得在没有管理令牌的情况下无法调用 doSensitiveThings
。 但我觉得我遗漏了一些重要的东西。
上面的代码比下面的代码好在哪里?
module Main where
import Data.Maybe
data Admin = Admin
data User a = User String
doSensitiveThings :: User Admin -> IO ()
doSensitiveThings _ = putStrLn "you did something sensitive"
trustedUser :: User Admin
trustedUser = User "Trust me"
untrustedUser :: User ()
untrustedUser = User "Don't trust me"
main = do
doSensitiveThings trustedUser
-- doSensitiveThings untrustedUser -- won't compile
用原代码可以这样写:
trustedUser = User (Just Proxy) "trusted"
untrustedUser = User Nothing "untrusted"
twoUsers :: [User] -- or Map Username User or whatever
twoUsers = [trustedUser, untrustedUser]
您不能使用第二个代码片段制作类似的 twoUsers
列表,因为您的受信任和不受信任的用户具有不同的类型。
好吧,现在没有“a User
”这样的东西了。 User Admin
和 User ()
现在有不同的类型,所以你不能像对待它们一样对待它们。列表元素:
users :: [User] -- ill-kinded!
users = [User "untrusted" :: User (), User "trusted" :: User Admin] -- ill-typed!
您也不能再根据用户是否是管理员进行分支(请记住 Haskell 是 类型擦除!):
displayActions :: User a -> [String]
displayActions (User name) =
["Delete My Account (" ++ name ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])
isAdmin :: User a -> Bool -- this function can take either User Admin or User ()...
isAdmin = ??? -- ...but how's it supposed to branch on that?
也许试试
data SomeUser = SomeAdmin (User Admin) | SomeNormalUser (User ())
但现在我们基本上在您的第一个示例中做同样的事情(其中 User Admin
成为令牌类型而不是 Proxy Admin
),只是更糟。只有很多代码噪音。
name :: SomeUser -> String -- having to write your own accessor functions over pattern matching/record fields; ew
name (SomeAdmin (User x)) = x
name (SomeNormalUser (User x)) = x -- ugly pattern matching and same code twice; ew
isAdmin :: SomeUser -> Bool
isAdmin (SomeAdmin _) = True
isAdmin _ = False
displayActions :: SomeUser -> [String] -- having both SomeUser and User instead of just one type and having to know which one to use in any given situation; ew
displayActions u =
["Delete My Account (" ++ name u ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])
我确实发现原版有问题,我相信这就是让您感到困惑的原因。原始代码中“唯一”的“好事”是令牌类型的存在。使用带有类型参数的 Proxy
来构造标记类型而不是
data AdminToken = AdminToken
(IMO)毫无意义且令人困惑(无论是对于理解技术还是在结果代码中)。类型参数 无关 与什么使这个想法好,并且通过保留类型参数而不是令牌,您将一无所获。我认为以下内容是对原作的 实际 改进,同时保持其好主意。
data User = { userAdminToken :: Maybe AdminToken; userName :: String }
isAdmin :: User -> Bool
isAdmin = isJust . userAdminToken
displayActions :: User -> [String]
displayActions u
["Delete My Account (" ++ userName u ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])
在 Maguire 的 Thinking with Types 中,p. 29,有一个如何使用提升的数据构造函数作为幻像参数的示例。这是我根据书中的例子写的一个模块。
{-# LANGUAGE DataKinds #-}
module Main where
import Data.Maybe
import Data.Proxy
-- | The only purpose of this constructor is to give us access to its
-- promoted data constructors.
data UserType = DummyUser | Admin
-- | Give some users an administration token.
data User = User
{ userAdminToken :: Maybe (Proxy 'Admin),
name :: String
}
doSensitiveThings :: Proxy 'Admin -> IO ()
doSensitiveThings _ = putStrLn "you did something sensitive"
trustedUser :: User
trustedUser = User (Just (Proxy :: Proxy Admin)) "Trust me"
main = do
doSensitiveThings (fromJust . userAdminToken $ trustedUser)
我知道这使得在没有管理令牌的情况下无法调用 doSensitiveThings
。 但我觉得我遗漏了一些重要的东西。
上面的代码比下面的代码好在哪里?
module Main where
import Data.Maybe
data Admin = Admin
data User a = User String
doSensitiveThings :: User Admin -> IO ()
doSensitiveThings _ = putStrLn "you did something sensitive"
trustedUser :: User Admin
trustedUser = User "Trust me"
untrustedUser :: User ()
untrustedUser = User "Don't trust me"
main = do
doSensitiveThings trustedUser
-- doSensitiveThings untrustedUser -- won't compile
用原代码可以这样写:
trustedUser = User (Just Proxy) "trusted"
untrustedUser = User Nothing "untrusted"
twoUsers :: [User] -- or Map Username User or whatever
twoUsers = [trustedUser, untrustedUser]
您不能使用第二个代码片段制作类似的 twoUsers
列表,因为您的受信任和不受信任的用户具有不同的类型。
好吧,现在没有“a User
”这样的东西了。 User Admin
和 User ()
现在有不同的类型,所以你不能像对待它们一样对待它们。列表元素:
users :: [User] -- ill-kinded!
users = [User "untrusted" :: User (), User "trusted" :: User Admin] -- ill-typed!
您也不能再根据用户是否是管理员进行分支(请记住 Haskell 是 类型擦除!):
displayActions :: User a -> [String]
displayActions (User name) =
["Delete My Account (" ++ name ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])
isAdmin :: User a -> Bool -- this function can take either User Admin or User ()...
isAdmin = ??? -- ...but how's it supposed to branch on that?
也许试试
data SomeUser = SomeAdmin (User Admin) | SomeNormalUser (User ())
但现在我们基本上在您的第一个示例中做同样的事情(其中 User Admin
成为令牌类型而不是 Proxy Admin
),只是更糟。只有很多代码噪音。
name :: SomeUser -> String -- having to write your own accessor functions over pattern matching/record fields; ew
name (SomeAdmin (User x)) = x
name (SomeNormalUser (User x)) = x -- ugly pattern matching and same code twice; ew
isAdmin :: SomeUser -> Bool
isAdmin (SomeAdmin _) = True
isAdmin _ = False
displayActions :: SomeUser -> [String] -- having both SomeUser and User instead of just one type and having to know which one to use in any given situation; ew
displayActions u =
["Delete My Account (" ++ name u ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])
我确实发现原版有问题,我相信这就是让您感到困惑的原因。原始代码中“唯一”的“好事”是令牌类型的存在。使用带有类型参数的 Proxy
来构造标记类型而不是
data AdminToken = AdminToken
(IMO)毫无意义且令人困惑(无论是对于理解技术还是在结果代码中)。类型参数 无关 与什么使这个想法好,并且通过保留类型参数而不是令牌,您将一无所获。我认为以下内容是对原作的 实际 改进,同时保持其好主意。
data User = { userAdminToken :: Maybe AdminToken; userName :: String }
isAdmin :: User -> Bool
isAdmin = isJust . userAdminToken
displayActions :: User -> [String]
displayActions u
["Delete My Account (" ++ userName u ++ ")"] ++ (if isAdmin u then ["Delete Someone Else's Account"] else [])