tl;dr
プラグマ内部での文字列リテラルを ::String
と型指定すればよい
{-# ANN module ("HLint: ignore Use camelCase" :: String) -#}
これに出くわした経緯
新しいプロジェクトで、テストスイートに tasty を使い、テストコードのディスカバリには tasty-discover を導入しようとしました。
tasty-discover
tasty-discover は ファイル名が *Test.hs
あるいは *Spec.hs
とサフィックスのついたモノをテスト対象のモジュールと判別し、その中で宣言されている prop_
spec_
などのプレフィックスのついた関数をテストスイートに自動的に追加してくれるというものです。
-
prop_
QuickCheck でテストしてくれる -
scprop_
SmallCheck でテストしてくれる -
unit_
HUnit でテスト -
spec_
Hspec -
test_
Tastyでこれら上記のテストをまとめる等
cabal ファイルのテストのビルド設定で main-is
に指定しているエントリーポイントには
{-#
OPTIONS_GHC -F -pgmF tasty-discover
-optF --debug --moduleSuffix=Spec
#-}
とか書いておくだけで、あとはテストファイルを追加していけば良いっていう感じになります
HLint の ANN
プラグマ指定
ここで、 prop_
とかのスネークケースな命名をすると、当然 HLint は「キャメルケース使え」って怒ってくるのですが、それを回避する方法として、モジュール内にGHCの ANN
プラグマを指定する方法があります。 ANN
は Annotation の意らしいです。
module Main where
import System.IO
--
--
{-# ANN module "HLint: ignore Use camelCase" #-}
hoge_huga :: a -- 怒られなくなる
なお、注意事項としては、 ANNプラグマは import
の後に書くようにしないとコンパイルが通りません。 気をつけよう
このように特定のモジュールや関数をLintの検査対象から外す使い方もあれば、逆に特定の対象にだけより強い厳格なルールを付与したりすることもできます。みんな使おうね
Megaparsec でTextをパースした結果をテストしたい→コンパイルできない→「!?」
で、 Megaparsec を利用して、Textをパースした結果のテストを書こうとして、このようにテストを書き始めたら、そもそもコンパイルができなくなってしまいました
{-# LANGUAGE OverloadedStrings #-}
module LexerSpec where
import Test.Tasty.Hspec
import Lexer
import Parser
{-# ANN module "HLint: ignore Use camelCase #-}
LexerSpec.hs:__:__: error:
• Ambiguous type variable ‘t0’ arising from the literal ‘"HLint: ignore Use camelCase"’
prevents the constraint ‘(Data.String.IsString
t0)’ from being solved.
Probable fix: use a type annotation to specify what ‘t0’ should be.
These potential instances exist:
instance a ~ Char => Data.String.IsString [a]
-- Defined in ‘Data.String’
...plus 10 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the annotation:
{-# ANN module "HLint: ignore Use camelCase" #-}
技術的解説
調べたところ、ちょうど2016年の頭にチケットがGHCに寄せられていました(修正の必要なしとしてクローズされています)
OverloadedStrings
の脱糖
↑ に書いてある通りですが、 OverloadedStrings
は文字列リテラルを糖衣構文とみなし、以下のような脱糖をします
"hello" -- このままだとただの [Char]
↓
(fromString ("hello" :: String)) -- この時の型は IsString a
なので、件のプラグマのコンパイルエラーが Ambiguous type variable
で始まっていたんですね。
{-# LANGUAGE OverloadedStrings #-}
-- ...
{-# ANN module ("HLint: ignore Use camelCase" :: String) -#}
こう書いた場合は
{-# ANN module ((fromString ("HLint: ignore Use camelCase" :: String)) :: String) -#}
たぶんこんな感じに展開されるのですが、冗長でも無事に型が特定できてコンパイルできるようになります。