Как я могу написать шаблон квази-квотер в Haskell? - PullRequest
0 голосов
/ 21 февраля 2019

Я использую квази-кавычки для создания своих умно сконструированных типов данных во время компиляции.Это выглядит примерно так:

import qualified Data.Text as T
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Language.Haskell.TH (Q, Exp, Pat(..), Lit(..))
import Language.Haskell.TH.Syntax (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH
import Instances.TH.Lift () -- th-lift-instances package

newtype NonEmptyText = NonEmptyText Text

textIsWhitespace :: Text -> Bool
textIsWhitespace = T.all (== ' ')

mkNonEmptyText :: Text -> Maybe NonEmptyText
mkNonEmptyText t = if textIsWhitespace t then Nothing else (Just (NonEmptyText t))

compileNonEmptyText :: QuasiQuoter
compileNonEmptyText = QuasiQuoter
  { quoteExp = compileNonEmptyText'
  , quotePat = error "NonEmptyText is not supported as a pattern"
  , quoteDec = error "NonEmptyText is not supported at top-level"
  , quoteType = error "NonEmptyText is not supported as a type"
  }
  where
    compileNonEmptyText' :: String -> Q Exp
    compileNonEmptyText' s = case mkNonEmptyText (pack s) of
      Nothing -> fail $ "Invalid NonEmptyText: " ++ s
      Just txt -> [| txt |]

(я могу предоставить автономный рабочий пример, если необходимо - я только что вытащил этот пример из большой кодовой базы)

По сути, просто выводя Liftдля моих новых типов я могу поместить тип данных в выражение квази-кавычку [| txt |] для реализации quoteExp.

Но у меня возникли проблемы с quotePat.Если я сделаю, например:

Just txt -> [p| txt |]

Тогда я получу предупреждение, что первый текст не используется, а второй затеняет первый.Я почти уверен, что этот шаблон просто создает новое имя txt, а не встраивает в область действия txt, как это сделал выражение квази-квотер, поскольку, когда я делаю:

f :: NonEmptyText -> Bool
f [compileNonEmptyText|test|] = True
f _ = False

всесоответствует первому утверждению.

1 Ответ

0 голосов
/ 21 февраля 2019

Хорошо, я думаю, что понял.Начиная с базовой строки s, я могу обернуть ее в StringL и LitP, чтобы получить литеральную строку, которая из-за Text 'IsString экземпляра станет Text.Оттуда мне нужно применить конструктор NonEmptyText, используя ConP:

compileNonEmptyTextPattern' :: String -> Q TH.Pat
compileNonEmptyTextPattern' s = case mkNonEmptyText (pack s) of
  Nothing -> fail $ "Invalid NonEmptyText: " ++ s
  Just (NonEmptyText txt) -> pure $ ConP 'NonEmptyText [(LitP (StringL (T.unpack txt)))]

К сожалению, это гораздо более многословно, чем версия выражения!Интересно, может ли быть класс типов для Q Pat, например, Lift для Q Exp?

...