22
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Haskell (その2)Advent Calendar 2018

Day 8

意外と知らないType defaulting

Last updated at Posted at 2018-12-07

error: Ambiguous type variable ‘a0’ ~

Haskellは高度な型推論を持っていますが、一部のケースではプログラマが明示的に型注釈を与えてやる必要があります。
典型的な例は次のようなreadの使用です。

main :: IO ()
main = do
  str <- getContents
  let val = read str :: Integer
  print val

これは標準入力から受け取った文字列を整数として解釈し、読み取った数値を標準出力に出力するプログラムです。read strに対して:: Integerと型注釈をつけることで、文字列を整数(Integer)として解釈することを明示しています。
この:: Integerが無いと、ghcは以下のようなエラーメッセージを出力します。

$ ghc Main.hs
[1 of 1] Compiling Main             ( Main.hs, Main.o )

Main.hs:4:13: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘read’
      prevents the constraint ‘(Read a0)’ from being solved.
      Relevant bindings include val :: a0 (bound at Main.hs:4:7)
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Read Ordering -- Defined in ‘GHC.Read’
        instance Read Integer -- Defined in ‘GHC.Read’
        instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
        ...plus 22 others
        ...plus 9 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: read str
      In an equation for ‘val’: val = read str
      In the expression:
        do str <- getContents
           let val = read str
           print val
  |
4 |   let val = read str
  |             ^^^^^^^^

Main.hs:5:3: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘print’
      prevents the constraint ‘(Show a0)’ from being solved.
      Relevant bindings include val :: a0 (bound at Main.hs:4:7)
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Show Ordering -- Defined in ‘GHC.Show’
        instance Show Integer -- Defined in ‘GHC.Show’
        instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
        ...plus 22 others
        ...plus 20 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In a stmt of a 'do' block: print val
      In the expression:
        do str <- getContents
           let val = read str
           print val
      In an equation for ‘main’:
          main
            = do str <- getContents
                 let val = ...
                 print val
  |
5 |   print val
  |   ^^^^^^^^^

注目するべきはAmbiguous type variable ‘a0’ arising from a use of ‘read’ prevents the constraint ‘(Read a0)’ from being solved.の部分です。
ghcは「ここで使用するReadのインスタンスを特定できない(曖昧である)」と言っています。
read strの返り値の型はReadのインスタンスの数だけあり得るため、明示しなければ分からない、ということです。
続く5行目のエラーメッセージはShowについて同様のエラーが生じています。
print valの型は(Show a, Read a) => IO ()ですが、=>の右辺にaが出現しない以上、aに当てはまる具体的な型を決定できない(Ambiguous type variable)のでコンパイルエラーが生じます。

Type defaulting

少しコードを修正し、読み取った数値に1を足したものを返すようにしてみます。

main :: IO ()
main = do
  str <- getContents
  let val = read str
  print $ val + 1

Haskellの数値リテラルおよび数値に対する二項演算子は多相的な型を持つため、
print $ val + 1の型は(Show a, Read a, Num a) => IO ()とNumに関する制約が増えただけで、曖昧性は解決されていないように見えます。
しかし、このコードは正しくコンパイルすることができます。これにはType defaultingという機能が大きく関わっています。

Type defaultingが実行されたかどうかは、コンパイルオプションに-Wtype-defaultsを加えることで確認できます。実際に先ほどのコードをコンパイルしてみましょう。

$ ghc -Wtype-defaults Main.hs
[1 of 1] Compiling Main             ( Main.hs, Main.o )

Main.hs:5:3: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Show a0) arising from a use of ‘print’ at Main.hs:5:3-17
        (Num a0) arising from a use of ‘+’ at Main.hs:5:11-17
        (Read a0) arising from a use of ‘read’ at Main.hs:4:13-20
    • In a stmt of a 'do' block: print $ val + 1
      In the expression:
        do str <- getContents
           let val = read str
           print $ val + 1
      In an equation for ‘main’:
          main
            = do str <- getContents
                 let val = ...
                 print $ val + 1
  |
5 |   print $ val + 1
  |   ^^^^^^^^^^^^^^^
Linking Main ...

曖昧だったa0がType defaultingによってIntegerに具体化されていることが分かります。

Type defaultingは次のルールに基づいて曖昧な型変数の具体化を行う機能です(Haskell 2010 Language Report1 4.3.4)。

曖昧な型変数vに対して以下の条件が成り立つ時、型の具体化を行う:

  • vC vの形の型制約にのみ現れる(Cは型クラス)
  • vを含む型制約に現れる型クラスのうち、少なくとも一つがnumeric class(Numかそのサブクラス)である
  • vを含む型制約に現れるすべての型クラスがPreludeモジュールか標準ライブラリで宣言されている

型の具体化は、モジュールごとのdefault宣言で定義されたリストを先頭から検証し、最初に型制約を満たした型を適用することで行う。

default宣言が存在しないモジュールでは、次のように宣言されているものとして扱う:

default (Integer, Double)

ghciのType defaulting

ghciでは、ExtendedDefaultRules拡張がデフォルトで有効になっています。
ExtendedDefaultRules拡張はType defaultingのルールを拡張することで、対話環境の使い勝手を改善するためのGHC拡張です。
普段からお世話になっている割に、かなりトリッキーな動作をするため、該当部分のドキュメント( https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html#type-defaulting-in-ghci )を一読することをおすすめします。

  1. https://www.haskell.org/onlinereport/haskell2010/

22
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?