我希望能够使用GHC IsString
扩展来生成OverloadedStrings
实例,这样我的实例就会拒绝某些文字无效,并且这样拒绝在编译时发生,因此编程错误不要将其写入我提供给用户的代码中。
我有几个用例,我的Name
类型只允许某些字符串。 e.g。
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
值的任何人都可以信任它具有某些属性(在这种情况下,非空)。
我的问题是我想在很多地方使用文字名称。 e.g。
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)
这种方法的问题在于,即使错误的原因是编程错误,我也不会发现它直到运行时间。
我想做的是为IsString
编写Name
个实例进行验证,例如。
instance IsString Name where
fromString = unsafeMakeName . Text.pack
...但是要在编译时获取有关文字中无效名称的错误。
当我尝试这个时,我似乎只在运行时使用文字值时得到错误。这不太理想,因为它在我的实际代码中是一个错误。
我有什么方法可以做到这一点?这是否可以在GHC中得到纠正?请注意,我已经filed a bug了。
答案 0 :(得分:16)
听起来你想要的是quasiquoter而不是OverloadedStrings
。验证逻辑然后进入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||]
请注意,您也可以获得适用于模式的quasiquoter(因此myFunc [name|valid-name|] = True
之类的函数可能是有效的函数定义)!