如何在IsString实例中获得文字的编译时验证?

jml*_*jml 11 haskell ghc

我希望能够IsString使用GHC OverloadedStrings扩展来创建实例,以便我的实例拒绝某些文字是无效的,并且这样拒绝在编译时发生,因此编程错误不会使它成为我给用户的代码.

我有几个用例,我的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
Run Code Online (Sandbox Code Playgroud)

在一个真实的用例中,我会检查有效字符,而不是以数字开头,这类事情.

我们的想法是不导出Name构造函数,这意味着使用Name值的任何人都可以信任它具有某些属性(在这种情况下,非空).

我的问题是我想在很多地方使用文字名称.例如

programName :: Name
programName = fromJust $ makeName "the-great-and-powerful-turtle"
Run Code Online (Sandbox Code Playgroud)

因为我做了很多,所以我定义了一个unsafeMakeName帮助器,它做了几乎相同的事情:

unsafeMakeName :: Text -> Name
unsafeMakeName name = fromMaybe (error $ "Invalid name: " <> Text.unpack name) (makeName name)
Run Code Online (Sandbox Code Playgroud)

这种方法的问题在于即使错误的原因是编程错误,我也不会发现它直到运行时.

我想做的是编写一个IsString实例进行Name验证,例如

instance IsString Name where
  fromString = unsafeMakeName . Text.pack
Run Code Online (Sandbox Code Playgroud)

...但是要在编译时获取文字中无效名称的错误.

当我尝试这个时,我似乎只在运行时使用文字值时得到错误.这不太理想,因为它在我的实际代码中是一个错误.

有什么方法可以做到这一点吗?这是否可以在GHC中得到纠正?请注意,我已经在那里提交了一个错误.

Ale*_*lec 16

这听起来像你想要的是一个quasiquoter而不是OverloadedStrings.验证逻辑然后进入Qmonad,它在编译时运行.对于上面的简单示例:

{-# 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 }
Run Code Online (Sandbox Code Playgroud)

然后,在另一个模块中,以下编译:

{-# LANGUAGE QuasiQuotes #-}
import Name

main = print [name|valid-name|]
Run Code Online (Sandbox Code Playgroud)

但以下没有,并吐出Invalid name错误消息.

{-# LANGUAGE QuasiQuotes #-}
import Name

main = print [name||]
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以获得适用于模式的quasiquoter(因此myFunc [name|valid-name|] = True可能是有效的函数定义)!