如何获得 IsString 实例中文字的编译时验证?
How can I get compile-time validation of literals in IsString instances?
我希望能够使用 GHC OverloadedStrings
扩展创建 IsString
个实例,这样我的实例就可以拒绝某些无效的文字,并且拒绝发生在编译时,因此编程错误不会出现在我提供给用户的代码中。
我有几个用例,其中 Name
类型只接受某些字符串。例如
module Name (Name(getName), makeName) where
import Data.Text (Text)
import qualified Data.Text as Text
-- | A guaranteed non-empty name.
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord)
makeName :: Text -> Maybe Name
makeName name
| Text.null name = Nothing
| otherwise = Just name
在实际用例中,我会检查有效字符,而不是以数字开头之类的东西。
我们的想法是我们不导出 Name
构造函数,这意味着任何使用 Name
值的人都可以相信它具有某些属性(在本例中为非空)。
我的问题是我想在很多地方使用文字名称。例如
programName :: Name
programName = fromJust $ makeName "the-great-and-powerful-turtle"
因为我经常这样做,所以我定义了一个 unsafeMakeName
助手来做几乎相同的事情:
unsafeMakeName :: Text -> Name
unsafeMakeName name = fromMaybe (error $ "Invalid name: " <> Text.unpack name) (makeName name)
这种方法的问题在于,即使错误的原因是编程错误,但直到 运行 时间我才发现。
我想做的是为 Name
编写一个 IsString
实例来进行验证,例如
instance IsString Name where
fromString = unsafeMakeName . Text.pack
... 但是在编译时得到有关文字中无效名称的错误。
当我尝试这个时,我似乎只在 运行 时得到错误,当使用文字值时。这不太理想,因为这是我实际代码中的一个错误。
有什么办法可以做到这一点吗?这是可以在 GHC 中解决的问题吗?请注意,我已经 filed a bug 了。
听起来你想要的确实是一个 quasiquoter and not OverloadedStrings
. The validation logic then goes inside the Q
monad,它在编译时运行。对于上面的简单示例:
{-# LANGUAGE QuasiQuotes, TemplateHaskell #-}
module Name (Name(getName), name) where
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Haskell.TH.Quote hiding (Name)
import Language.Haskell.TH hiding (Name)
-- | A guaranteed non-empty name.
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord)
makeName :: String -> Q Exp
makeName name
| null name = fail "Invalid name"
| otherwise = [| Name (Text.pack name) |]
name :: QuasiQuoter
name = QuasiQuoter { quoteExp = makeName }
然后,在另一个模块中,编译以下内容:
{-# LANGUAGE QuasiQuotes #-}
import Name
main = print [name|valid-name|]
但下面没有,并吐出 Invalid name
错误信息。
{-# LANGUAGE QuasiQuotes #-}
import Name
main = print [name||]
请注意,您也可以获得适用于模式的准引号(因此 myFunc [name|valid-name|] = True
之类的内容可能是有效的函数定义)!
我希望能够使用 GHC OverloadedStrings
扩展创建 IsString
个实例,这样我的实例就可以拒绝某些无效的文字,并且拒绝发生在编译时,因此编程错误不会出现在我提供给用户的代码中。
我有几个用例,其中 Name
类型只接受某些字符串。例如
module Name (Name(getName), makeName) where
import Data.Text (Text)
import qualified Data.Text as Text
-- | A guaranteed non-empty name.
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord)
makeName :: Text -> Maybe Name
makeName name
| Text.null name = Nothing
| otherwise = Just name
在实际用例中,我会检查有效字符,而不是以数字开头之类的东西。
我们的想法是我们不导出 Name
构造函数,这意味着任何使用 Name
值的人都可以相信它具有某些属性(在本例中为非空)。
我的问题是我想在很多地方使用文字名称。例如
programName :: Name
programName = fromJust $ makeName "the-great-and-powerful-turtle"
因为我经常这样做,所以我定义了一个 unsafeMakeName
助手来做几乎相同的事情:
unsafeMakeName :: Text -> Name
unsafeMakeName name = fromMaybe (error $ "Invalid name: " <> Text.unpack name) (makeName name)
这种方法的问题在于,即使错误的原因是编程错误,但直到 运行 时间我才发现。
我想做的是为 Name
编写一个 IsString
实例来进行验证,例如
instance IsString Name where
fromString = unsafeMakeName . Text.pack
... 但是在编译时得到有关文字中无效名称的错误。
当我尝试这个时,我似乎只在 运行 时得到错误,当使用文字值时。这不太理想,因为这是我实际代码中的一个错误。
有什么办法可以做到这一点吗?这是可以在 GHC 中解决的问题吗?请注意,我已经 filed a bug 了。
听起来你想要的确实是一个 quasiquoter and not OverloadedStrings
. The validation logic then goes inside the Q
monad,它在编译时运行。对于上面的简单示例:
{-# LANGUAGE QuasiQuotes, TemplateHaskell #-}
module Name (Name(getName), name) where
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Haskell.TH.Quote hiding (Name)
import Language.Haskell.TH hiding (Name)
-- | A guaranteed non-empty name.
newtype Name = Name { getName :: Text } deriving (Eq, Show, Ord)
makeName :: String -> Q Exp
makeName name
| null name = fail "Invalid name"
| otherwise = [| Name (Text.pack name) |]
name :: QuasiQuoter
name = QuasiQuoter { quoteExp = makeName }
然后,在另一个模块中,编译以下内容:
{-# LANGUAGE QuasiQuotes #-}
import Name
main = print [name|valid-name|]
但下面没有,并吐出 Invalid name
错误信息。
{-# LANGUAGE QuasiQuotes #-}
import Name
main = print [name||]
请注意,您也可以获得适用于模式的准引号(因此 myFunc [name|valid-name|] = True
之类的内容可能是有效的函数定义)!