Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

AllowAmbiguousTypes拡張と型適用

このように f を定義して実行したい。

f :: String -> String
f = show . read

main :: IO ()
main = putStrLn $ f "True"

エラーが出る。

Prelude> :l test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:5:5: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘show’
      prevents the constraint ‘(Show a0)’ from being solved.
      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 16 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘(.)’, namely ‘show’
      In the expression: show . read
      In an equation for ‘f’: f = show . read

test.hs:5:12: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘read’
      prevents the constraint ‘(Read a0)’ from being solved.
      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 7 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘(.)’, namely ‘read’
      In the expression: show . read
      In an equation for ‘f’: f = show . read
Failed, modules loaded: none.

showread の対象とする型 a0 がきちんと決まってないのが原因なので、これを f に対して指定できるようにする。

f :: (Show a, Read a) => String -> String
f = (show :: a -> String) . read

再びエラーとなる。

Prelude> :r
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:4:6: error:
    • Could not deduce (Read a0)
      from the context: (Show a, Read a)
        bound by the type signature for:
                   f :: (Show a, Read a) => String -> String
        at test.hs:4:6-41
      The type variable ‘a0’ is ambiguous
    • In the ambiguity check for ‘f’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      In the type signature:
        f :: (Show a, Read a) => String -> String
Failed, modules loaded: none.

aShowReadインスタンスだと言ってる割には、型の本文に a が登場しないのでこれを決定する方法がない。 AllowAmbiguousTypes を使うとこのチェックを遅らせられるよと教えてくれているので、有効にする。 が、これまたゴツいエラー。

Prelude> :r
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:5:6: error:
    • Could not deduce (Show a2) arising from a use of ‘show’
      from the context: (Show a, Read a)
        bound by the type signature for:
                   f :: (Show a, Read a) => String -> String
        at test.hs:4:1-41
      Possible fix:
        add (Show a2) to the context of
          an expression type signature:
            a2 -> String
    • In the first argument of ‘(.)’, namely ‘(show :: a -> String)’
      In the expression: (show :: a -> String) . read
      In an equation for ‘f’: f = (show :: a -> String) . read

test.hs:5:29: error:
    • Could not deduce (Read a0) arising from a use of ‘read’
      from the context: (Show a, Read a)
        bound by the type signature for:
                   f :: (Show a, Read a) => String -> String
        at test.hs:4:1-41
      The type variable ‘a0’ is ambiguous
      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 7 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘(.)’, namely ‘read’
      In the expression: (show :: a -> String) . read
      In an equation for ‘f’: f = (show :: a -> String) . read
Failed, modules loaded: none.

これは show :: a -> String が暗黙的に show :: forall a. a -> String と解釈されているのが原因なので、 ScopedTypeVariables 拡張を使う。

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

f :: forall a. (Show a, Read a) => String -> String
f = (show :: a -> String) . read

main :: IO ()
main = putStrLn $ f "True"

f の定義についてのエラーはなくなった。後は呼び出し側。

*Main> :r
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:8:19: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘f’
      prevents the constraint ‘(Read a0)’ from being solved.
      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 7 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘($)’, namely ‘f "True"’
      In the expression: putStrLn $ f "True"
      In an equation for ‘main’: main = putStrLn $ f "True"
Failed, modules loaded: none.

a を指定すればいいのだから、ここで TypeApplications の登場。

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

f :: forall a. (Show a, Read a) => String -> String
f = (show :: a -> String) . read

main :: IO ()
main = putStrLn $ f @Bool "True"
Prelude> :r
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> :main
True

めでたしめでたし。せっかく TypeApplications を使うなら、 f の定義ももう少し簡潔になる。

f :: forall a. (Show a, Read a) => String -> String
f = show @a . read

余談1. AllowAmbiguousTypes の妥当性

型変数作って TypeApplications 使って適用したいだけなのに突然 AllowAmbiguousTypes 拡張が出てきてなんだよって思ったかもしれないが、 論文中ではこいつの必要性は書いてあって、そもそもは TypeApplications を有効にすると自動的に有効になるようになってた。

このへんのメールのやり取り でない方がいいだろって話になって、 このコミットで消された。

余談2. Proxy

TypeApplications を使えるコンパイラを持っていないあなたでも、型の役割をする式である Proxy 型を使うことができるのだ! 副作用で型の本文に a が入るようになるので、 AllowAmbiguousTypes 拡張も不要になる。

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Proxy

f :: forall proxy a. (Show a, Read a) => proxy a -> String -> String
f _ = (show :: a -> String) . read

main :: IO ()
main = putStrLn $ f (Proxy :: Proxy Bool) "True"

だが、勘がいいあなたなら気がついてしまったかもしれない。 Proxy は可読性をあげてくれるだけで、なくても困らないことを。

{-# LANGUAGE ScopedTypeVariables #-}

f :: forall proxy a. (Show a, Read a) => proxy a -> String -> String
f _ = (show :: a -> String) . read

main :: IO ()
main = putStrLn $ f [False] "True"

余談3. Type defaulting in GHCi

GHCiのデフォルト型の割り当ては強力 なので、なんと f がそのままコンパイルできてしまうのだ!! ただし、勝手に () になってしまう。

*Main> f = show . read
*Main> f "()"
"()"
*Main> f "True"
"*** Exception: Prelude.read: no parse