Haskell
ghc
hlint
template-haskell
HaskellDay 24

アンッ!!!アンッ!!!!

More than 3 years have passed since last update.


メリークリスマス

はい、クリスマスイブです。

日本のカップルはクリスマス頃になると競ってアンアンしだすと言います1ので、今年のクリスマスはお一人の方も是非アンアンしましょう。


アンアン?

アンアンと言えば、もちろんghcのANNプラグマですね。

Template Haskellからでも見る事が出来る様になって美味み増していると思うので、色々な所から覗いてみましょう。


ANNプラグマ?

ANNプラグマは、値、型、モジュールに注釈を入れる為のプラグマです。


ann.hs

module Foo where

{-# ANN module ("annotation" :: String) #-} -- モジュールに対する注釈。importの前には書けないっぽい。不便……

data Foo = Foo
{-# ANN type Foo (2 :: Int) #-} -- 型に対する注釈
{-# ANN type Foo (5 :: Int) #-} -- 注釈を同じ/違う型で複数個付ける事も出来る
{-# ANN type Foo (2.4 :: Double) #-}

foo :: Foo
foo = Foo
{-# ANN foo (3 + 2 * 6 :: Int) #-} -- 値に対する注釈。注釈の中で計算する事も可能


注釈にはDataクラスのインスタンスであれば何でも入れる事が出来ます。

DataクラスはbaseData.Dataモジュールにて定義されているジェネリックプログラミングの為のクラスですが、今回は特にそれらしい使い方はしません。

ですので以下の様にすれば、自分で定義した型についてもDataクラスのインスタンスを導出出来る事だけを抑えておけば良いでしょう。

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Data

data Foo = Foo
deriving(Typeable, Data)

実際に使用する時はこの様にアノーテーション用の型を提供して、他との混同を避けるのが良いかと思われます。


template-haskellから

まずはtemplate-haskellから覗いてみましょう。


th.hs

{-# LANGUAGE TemplateHaskell #-}

module Foo where

import Language.Haskell.TH

{-# ANN module ("annotation" :: String) #-}

data Foo = Foo
{-# ANN type Foo (2 :: Int) #-}
{-# ANN type Foo (5 :: Int) #-}
{-# ANN type Foo (2.4 :: Double) #-}

foo :: Foo
foo = Foo
{-# ANN foo (3 + 2 * 6 :: Int) #-}

$(do -- テンプレートハスケル。コンパイル時に実行される

mdl <- thisModule

-- このモジュールのString型のアノーテーションを取得
mdlAnn <- reifyAnnotations (AnnLookupModule mdl) :: Q [String]

-- Foo型構築子のInt型のアノーテーションを取得
typeAnn <- reifyAnnotations (AnnLookupName ''Foo) :: Q [Int]

-- fooのInt型のアノーテーションを取得
valAnn <- reifyAnnotations (AnnLookupName 'foo) :: Q [Int]

-- それらを出力して死ぬ
fail $ unlines
[ "module: " ++ show mdlAnn
, "type: " ++ show typeAnn
, "val: " ++ show valAnn
]
)


$ ghc th.hs

[1 of 1] Compiling Foo ( th.hs, th.o )

th.hs:16:3: module: ["annotation"]
type: [5,2]
val: [15]

この様に、目当ての型のアノーテーションを、複数個取って来る事が出来ます。

色々楽しげな事が出来そうですね!!!!!!!!2


haskell-src-extsから

haskell-src-extsでパーズした結果にもANNプラグマの内容は入っています。

とても適当ですが、先のann.hsをパーズしてみましょう。


hse.hs

{-# LANGUAGE LambdaCase #-}

import Language.Haskell.Exts

main :: IO ()
main = parseFile "ann.hs" >>= \case
ParseFailed _ s -> putStrLn s
ParseOk (Module _ _ _ _ _ _ d) -> mapM_ print d

-- data Module = Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]


$ runhaskell hse.hs

AnnPragma (SrcLoc "ann.hs" 2 1) (ModuleAnn (Paren (ExpTypeSig (SrcLoc "ann.hs" 2 17) (Lit (String "annotation")) (TyCon (UnQual (Ident "String"))))))
DataDecl (SrcLoc "ann.hs" 4 1) DataType [] (Ident "Foo") [] [QualConDecl (SrcLoc "ann.hs" 4 12) [] [] (ConDecl (Ident "Foo") [])] []
AnnPragma (SrcLoc "ann.hs" 5 1) (TypeAnn (Ident "Foo") (Paren (ExpTypeSig (SrcLoc "ann.hs" 5 19) (Lit (Int 2)) (TyCon (UnQual (Ident "Int"))))))
AnnPragma (SrcLoc "ann.hs" 6 1) (TypeAnn (Ident "Foo") (Paren (ExpTypeSig (SrcLoc "ann.hs" 6 19) (Lit (Int 5)) (TyCon (UnQual (Ident "Int"))))))
AnnPragma (SrcLoc "ann.hs" 7 1) (TypeAnn (Ident "Foo") (Paren (ExpTypeSig (SrcLoc "ann.hs" 7 19) (Lit (Frac (12 % 5))) (TyCon (UnQual (Ident "Double"))))))
TypeSig (SrcLoc "ann.hs" 9 1) [Ident "foo"] (TyCon (UnQual (Ident "Foo")))
PatBind (SrcLoc "ann.hs" 10 1) (PVar (Ident "foo")) (UnGuardedRhs (Con (UnQual (Ident "Foo")))) (BDecls [])
AnnPragma (SrcLoc "ann.hs" 11 1) (Ann (Ident "foo") (Paren (ExpTypeSig (SrcLoc "ann.hs" 11 14) (InfixApp (Lit (Int 3)) (QVarOp (UnQual (Symbol "+"))) (InfixApp (Lit (Int 2)) (QVarOp (UnQual (Symbol "*"))) (Lit (Int 6)))) (TyCon (UnQual (Ident "Int"))))))

はい、しっかり各アノーテーションが取れていますね。

これはhlintの出力をモジュール、型、値単位で制御するために使用されています。


warn.hs

ident a = (a)


$ hlint warn.hs

hl-test.hs:1:11: Error: Redundant bracket
Found:
(a)
Why not:
a

1 suggestion


ignore.hs

{-# ANN foo "HLint: ignore" #-}

ident a = (a)

$ hlint ignore.hs

No suggestions (1 ignored)

この様に、特別なコメントなどで制御するのは避けて、ANNプラグマで制御する方が良いでしょう。


まとめ


  • タイトルだけでアレすると酷い事になる

  • ANN、色々面白げなのでどんどん使ってくださいおねがいします


read more



  • 9.3. Compiler Plugins -- ghc-pluginからも覗けます。アグレッシブな最適化とかに使うっぽい3






  1. 性の6時間とは (ヤリマクリスマスとは) [単語記事] - ニコニコ大百科 



  2. THの引数の代わりにANNを使用する事で、どこからTHを読んでも設定が同じになるのが特徴っぽい(aesonのderiveとかに向いてそう? 



  3. もう無いっぽいが、haskell-jp:595SpecConstrとか?