在 Haskell 中使用 Data.Reflection 时由于类型不明确而无法显示
Can't show due to ambiguous type while using Data.Reflection in Haskell
我是 Haskell 的新手,我正在尝试创建一个类型来表示 Integral 在某个模数上的任何实例。我找到 some example code online 并正在使用它,所以我的类型定义如下所示:
data Zn n a = Zn !a !a
大多数事情都如我所愿;我会显示、加、减等。
instance (Integral a, Show a) => Show (Zn n a) where
show (Zn n x) = printf "(%s mod %s)" (show (mod x n)) (show n)
instance (Integral a, Reifies n a) => Num (Zn n a) where
Zn n x + Zn _ y = Zn n (mod (x + y) n)
Zn n x - Zn _ y = Zn n (mod (x - y) n)
Zn n x * Zn _ y = Zn n (mod (x * y) n)
negate (Zn n x) = Zn n (n - x)
abs = id
signum x@(Zn _ 0) = x
signum (Zn n _) = Zn n 1
fromInteger x = Zn n (mod (fromInteger x) n)
where n = reflect (Proxy :: Proxy n)
znToIntegral :: Integral a => Zn n a -> a
znToIntegral (Zn n x) = fromIntegral x
但是,我无法显示这些类型的算术运算结果。例如,在 GHCi 中:
*Main> let x = Zn 5 3
*Main> x
(3 mod 5)
*Main> let y = Zn 5 7
(2 mod 5)
*Main> let z = x + y
*Main> z
<interactive>:6:1:
No instance for (Integral a0) arising from a use of ‘print’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
instance Integral GHC.Int.Int16 -- Defined in ‘GHC.Int’
instance Integral GHC.Int.Int32 -- Defined in ‘GHC.Int’
instance Integral GHC.Int.Int64 -- Defined in ‘GHC.Int’
...plus 9 others
In a stmt of an interactive GHCi command: print it
我发现这个问题在我尝试实现这些数字的许多其他方式中出现,并且了解 Data.Reflection 包的工作原理给我带来了一些麻烦。我也会对其他人看起来更自然的任何其他实现感到好奇。我最初尝试做类似
的事情
newtype Zn n a = Zn a
然后切换,因为我认为它会简化事情,但事实并非如此。干杯!
在此特定示例中,Zn
是一个内部实现细节,不应将其用于构造数字。理想情况下,Zn
甚至不应该从库中导出。相反,我们应该使用 fromInteger
和数字文字来构造。
模数是通过反射在fromInteger
中引入的,我们只将其存储在Zn
中以避免进一步使用reflect
,出于性能原因(虽然我相信reflect
默认情况下没有太多开销,所以我们可能不会从这个方案中获得太多)。如果我们只使用 fromInteger
来创建新的 Zn
-s,模数将在整个计算中与 Reifies
约束一致。另一方面,如果我们手动将模数插入 Zn
,那么那部分 Zn
将始终具有该模数,而 reify
(我们提供隐式配置的方式)根本不会影响它。换句话说,它打乱了我们假设的不变量。
使用示例:
foo :: (Integral a, Reifies s a) => Zn s a -> Zn s a -> Zn s a
foo a b = e where
c = 123
d = a * b - 3
e = negate (d + c)
showAFooResult = reify 12 (\(Proxy :: Proxy s) -> show (foo 3 4 :: Zn s Int))
-- fooResult == "(0 mod 12)"
在我们使用 reify
插入后,在 foo
的主体中,所有内容都将具有 12
作为模数。
如果我们仔细观察,foo
只是一个函数,它需要一个额外的 Integral
参数,它在内部用作模数。我们可以使用 reify
来提供参数,使用 Proxy
中的 s
索引。
如果我们不提供任何值来插入 Reifies
孔,那么我们就无法提取值。当然我们不能打印它,就像我们不能打印函数一样。
我们可以打印 Zn 5 5
因为它的类型是 Num a => Zn n a
,所以我们没有空洞可以填补。另一方面,Zn 5 5 + Zn 5 5
具有类型 (Integral a, Reifies n a) => Zn n a
,因为我们在 Num
实例中指定了 Reifies n a
约束,当然 +
的使用需要一个Num
实例。在这里,我们必须使用 reify
(或调用它的其他辅助函数)来提取结果。
为了比较,这里有一个不存储模数并始终使用 reflect
:
的实现
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
import Data.Reflection
import Data.Proxy
import Text.Printf
newtype Zn a s = Zn { getZ :: a }
instance (Integral a, Show a, Reifies s a) => Show (Zn a s) where
show x =
let
p = reflect (Proxy::Proxy s)
in
printf "(%s mod %s)" (show (mod (getZ x) p)) (show p)
instance (Integral a, Reifies s a) => Num (Zn a s) where
Zn x + Zn y = Zn (mod (x + y) (reflect (Proxy::Proxy s)))
Zn x - Zn y = Zn (mod (x - y) (reflect (Proxy::Proxy s)))
Zn x * Zn y = Zn (mod (x * y) (reflect (Proxy::Proxy s)))
negate (Zn x) = Zn ((reflect (Proxy::Proxy s)) - x)
abs = id
signum x@(Zn 0) = x
signum _ = Zn 1
fromInteger x = Zn (mod (fromInteger x) p)
where p = reflect (Proxy :: Proxy s)
一些辅助功能:
znToIntegral :: Integral a => Zn a s -> a
znToIntegral (Zn x) = fromIntegral x
-- Convince the compiler that the phantom type in the proxy
-- is the same as the one in the Zn
likeProxy :: Proxy s -> Zn a s -> Zn a s
likeProxy _ = id
withZn :: Integral a => a -> (forall s. Reifies s a => Zn a s) -> a
withZn p z = reify p $ \proxy -> znToIntegral . likeProxy proxy $ z
使用示例:
main :: IO ()
main = print $ withZn (7::Int) (Zn 3 + Zn 5)
withZn
也适用于 ghci。
我是 Haskell 的新手,我正在尝试创建一个类型来表示 Integral 在某个模数上的任何实例。我找到 some example code online 并正在使用它,所以我的类型定义如下所示:
data Zn n a = Zn !a !a
大多数事情都如我所愿;我会显示、加、减等。
instance (Integral a, Show a) => Show (Zn n a) where
show (Zn n x) = printf "(%s mod %s)" (show (mod x n)) (show n)
instance (Integral a, Reifies n a) => Num (Zn n a) where
Zn n x + Zn _ y = Zn n (mod (x + y) n)
Zn n x - Zn _ y = Zn n (mod (x - y) n)
Zn n x * Zn _ y = Zn n (mod (x * y) n)
negate (Zn n x) = Zn n (n - x)
abs = id
signum x@(Zn _ 0) = x
signum (Zn n _) = Zn n 1
fromInteger x = Zn n (mod (fromInteger x) n)
where n = reflect (Proxy :: Proxy n)
znToIntegral :: Integral a => Zn n a -> a
znToIntegral (Zn n x) = fromIntegral x
但是,我无法显示这些类型的算术运算结果。例如,在 GHCi 中:
*Main> let x = Zn 5 3
*Main> x
(3 mod 5)
*Main> let y = Zn 5 7
(2 mod 5)
*Main> let z = x + y
*Main> z
<interactive>:6:1:
No instance for (Integral a0) arising from a use of ‘print’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
instance Integral GHC.Int.Int16 -- Defined in ‘GHC.Int’
instance Integral GHC.Int.Int32 -- Defined in ‘GHC.Int’
instance Integral GHC.Int.Int64 -- Defined in ‘GHC.Int’
...plus 9 others
In a stmt of an interactive GHCi command: print it
我发现这个问题在我尝试实现这些数字的许多其他方式中出现,并且了解 Data.Reflection 包的工作原理给我带来了一些麻烦。我也会对其他人看起来更自然的任何其他实现感到好奇。我最初尝试做类似
的事情newtype Zn n a = Zn a
然后切换,因为我认为它会简化事情,但事实并非如此。干杯!
在此特定示例中,Zn
是一个内部实现细节,不应将其用于构造数字。理想情况下,Zn
甚至不应该从库中导出。相反,我们应该使用 fromInteger
和数字文字来构造。
模数是通过反射在fromInteger
中引入的,我们只将其存储在Zn
中以避免进一步使用reflect
,出于性能原因(虽然我相信reflect
默认情况下没有太多开销,所以我们可能不会从这个方案中获得太多)。如果我们只使用 fromInteger
来创建新的 Zn
-s,模数将在整个计算中与 Reifies
约束一致。另一方面,如果我们手动将模数插入 Zn
,那么那部分 Zn
将始终具有该模数,而 reify
(我们提供隐式配置的方式)根本不会影响它。换句话说,它打乱了我们假设的不变量。
使用示例:
foo :: (Integral a, Reifies s a) => Zn s a -> Zn s a -> Zn s a
foo a b = e where
c = 123
d = a * b - 3
e = negate (d + c)
showAFooResult = reify 12 (\(Proxy :: Proxy s) -> show (foo 3 4 :: Zn s Int))
-- fooResult == "(0 mod 12)"
在我们使用 reify
插入后,在 foo
的主体中,所有内容都将具有 12
作为模数。
如果我们仔细观察,foo
只是一个函数,它需要一个额外的 Integral
参数,它在内部用作模数。我们可以使用 reify
来提供参数,使用 Proxy
中的 s
索引。
如果我们不提供任何值来插入 Reifies
孔,那么我们就无法提取值。当然我们不能打印它,就像我们不能打印函数一样。
我们可以打印 Zn 5 5
因为它的类型是 Num a => Zn n a
,所以我们没有空洞可以填补。另一方面,Zn 5 5 + Zn 5 5
具有类型 (Integral a, Reifies n a) => Zn n a
,因为我们在 Num
实例中指定了 Reifies n a
约束,当然 +
的使用需要一个Num
实例。在这里,我们必须使用 reify
(或调用它的其他辅助函数)来提取结果。
为了比较,这里有一个不存储模数并始终使用 reflect
:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
import Data.Reflection
import Data.Proxy
import Text.Printf
newtype Zn a s = Zn { getZ :: a }
instance (Integral a, Show a, Reifies s a) => Show (Zn a s) where
show x =
let
p = reflect (Proxy::Proxy s)
in
printf "(%s mod %s)" (show (mod (getZ x) p)) (show p)
instance (Integral a, Reifies s a) => Num (Zn a s) where
Zn x + Zn y = Zn (mod (x + y) (reflect (Proxy::Proxy s)))
Zn x - Zn y = Zn (mod (x - y) (reflect (Proxy::Proxy s)))
Zn x * Zn y = Zn (mod (x * y) (reflect (Proxy::Proxy s)))
negate (Zn x) = Zn ((reflect (Proxy::Proxy s)) - x)
abs = id
signum x@(Zn 0) = x
signum _ = Zn 1
fromInteger x = Zn (mod (fromInteger x) p)
where p = reflect (Proxy :: Proxy s)
一些辅助功能:
znToIntegral :: Integral a => Zn a s -> a
znToIntegral (Zn x) = fromIntegral x
-- Convince the compiler that the phantom type in the proxy
-- is the same as the one in the Zn
likeProxy :: Proxy s -> Zn a s -> Zn a s
likeProxy _ = id
withZn :: Integral a => a -> (forall s. Reifies s a => Zn a s) -> a
withZn p z = reify p $ \proxy -> znToIntegral . likeProxy proxy $ z
使用示例:
main :: IO ()
main = print $ withZn (7::Int) (Zn 3 + Zn 5)
withZn
也适用于 ghci。