haskell-jp / beginners #23 at 2023-05-06 01:20:47 +0900

型の異なる値の計算について教えて下さい

以下の関数 f は a, b に対し fromIntegral を適用することで (+) の引数とできます。
import Data.Word
import Data.Function ( on )
import Control.Monad ( liftM2 )

f = do
    let a = pure 1 :: Maybe Word32
    let b = pure 2 :: Maybe Word64

    let a' = fromIntegral <$> a
    let b' = fromIntegral <$> b

    let c = (+) <$> a' <*> b'

    print c
---
ghci> f
Just 3
it :: ()

これを on 関数を使って一気に適用すると a, b の型が異なるため不可能です。
g = do
    let a = pure 1 :: Maybe Word32
    let b = pure 2 :: Maybe Word64

    let c = on (liftM2 (+)) (fromIntegral <$>) a b

    print c
---
a.hs:23:50: error:
    • Couldn't match type 'Word64' with 'Word32'
      Expected: Maybe Word32
        Actual: Maybe Word64
    • In the fourth argument of 'on', namely 'b'
      In the expression: on (liftM2 (+)) (fromIntegral <$>) a b
      In an equation for 'c': c = on (liftM2 (+)) (fromIntegral <$>) a b
   |
23 |     let c = on (liftM2 (+)) (fromIntegral <$>) a b
   |   

このような場合、なにか上手に解決する方法はあるのでしょうか ?
Maybe をなくしてもエラーは本質的に変わらなさそう () なので、以降 Maybe を省いて書きます。

on によって 2つの引数は同じ型に推論される

on (+) fromIntegral :: (Integral a, Num c) => a -> a -> c

ので、 on を使うとこのエラーは避けられないですね。そこで、やたらポリモーフィックにした

genericOn :: (c a, c b) => (d -> d -> e) -> (forall x. c x => x -> d) -> a -> b -> e
genericOn (.*.) f x y = f x .*. f y

を作ることで fromIntegral の出現を一回だけにできました ()。

ただし、実用的かは疑問です。型推論のために Integral を TypeApplication で与えないといけなかったですし…
G/W の深夜にも関わらず、ご回答ありがとうございました :pray:

fromIntegral が便利なのに、やはり on とは合わせられないのですね
forall については、ちらっと見たことがあったのですが、実際に
どのように使うのか理解できませんでした。

まだ、すぐには使えるようにならないと思いますが
教えていただいたコードをもとに勉強してみます。
@Integral が TypeApplication というものなんですね

こちらは、初めてみましたが探してみると forall と一緒に使った例が
見つかるので、こちらも勉強してみます。
この例だけだとむしろ g = (fromIntegral <$>) を定義して c = (+) <$> g a <*> g b のように書くのが一番楽なのでは?と感じました。ただ、この g の定義のときに g :: (Functor f, Integral a, Num b) => f a -> f b のような注釈をつけないと Ambiguous と言われるのでそこらへんがややこしさの原因かなという気はします
ご回答ありがとうございます。
こちらの方法であれば理解はしやすいです :grinning:

同じことを実現するにしても様々な方法があるようなので
場合によって使い分けてみます
この例だけだとむしろ[…] のように書くのが一番楽なのでは?
そうですよね… :sweat_smile:
この話題、 map fromIntegral [a, b] ってできないっていうのと同じのように見えるんですよね。要は、 a と b の型をそろえない限りはこういうことはできない(型クラスはあくまで型クラスであって subtype ではない)ので、結局個別に型変換が必要になりますよ、という感じで。
map を例にしてもらうとわかりやすいです。
ありがとうございます。

fromIntegral が Num にしてくれるので、ついそちらを
基準に考えてしまいました