haskell-jp / questions #105 at 2023-11-28 23:03:00 +0900

以下の (f $ g) はどのように理解できますか?
ghci> let f = (1 +) :: (Num a) => a -> a
ghci> let g = (2 +) :: (Num a) => a -> a
ghci> :t (f $ g)
(f $ g) :: (Num a, Num (a -> a)) => a -> a

背景としては、以下のようにコンパイルエラーになる例を考えるつもりでした:
ghci> let a = (1 +) :: Int -> Int
ghci> let b = (2 +) :: Int -> Int
ghci> :t (a $ b)

<interactive>:1:6: error:
    • Couldn't match expected type 'Int' with actual type 'Int -> Int'
    • In the second argument of '($)', namely 'b'
      In the expression: a $ b
f の型シグネチャにある2つの型変数 a は同じ型に具体化され、gの型シグネチャにある2つの型変数 a も同じ型に具体化されますが、スコープは別になります。
実際、
instance Num a => Num (a -> a) where
    (+) :: Num a => (a -> a) -> (a -> a) -> a -> a
    f + g = (+) . f <*> g
    (*) :: Num a => (a -> a) -> (a -> a) -> a -> a
    f * g = (*) . f <*> g
    negate :: Num a => (a -> a) -> a -> a
    negate f = negate . f
    abs :: Num a => (a -> a) -> a -> a
    abs f = abs . f
    signum :: Num a => (a -> a) -> a -> a
    signum f = signum . f
    fromInteger :: Num a => Integer -> a -> a
    fromInteger = const . fromInteger

のように定義すると
ghci> let f = (1 +) :: a -> a
ghci> let g = (2 +) :: a -> a
ghci> :t f g
f g :: Num a => a -> a

のように型なり、
ghci> f g (0 :: Int)
3

となります。
ここで、f は (Int -> Int) -> (Int -> Int) に具体化され、g は Int -> Int に具体化されているます。
a -> a は Int 具体化できないので、f :: Int -> Int とするとエラーになります。
数値リテラルの 1fromInteger を経由して Int -> Int になるというのは、盲点というかショックでした。 Num の定義の具体例もありがとうございます! `const` を活かして関数合成のような関数適用になっているのが面白かったです。
GHC は、よく僕の認知を超えた答えを出すので混乱します 笑
GHC に見合った人間を目指さなければ…… >_<