gpt4 book ai didi

haskell - 消除相似求和类型方法之间的代码重复

转载 作者:行者123 更新时间:2023-12-04 00:50:58 25 4
gpt4 key购买 nike

问题

我正在实现 Scheme,我的数字塔的一部分看起来像这样:

data MyNumber
= MyInt Integer
| MyFloat Float

instance Num MyNumber where
abs = \case
MyInt val -> MyInt $ abs val
MyFloat val -> MyFloat $ abs val

signum = \case
MyInt val -> MyInt $ signum val
MyFloat val -> MyFloat $ signum val

negate = \case
MyInt val -> MyInt $ negate val
MyFloat val -> MyFloat $ negate val

(+) a b = case (a, b) of
(MyInt a', MyInt b') -> MyInt $ a' + b'
(MyInt a', MyFloat b') -> MyFloat $ fromInteger a' + b'
(MyFloat a', MyInt b') -> MyFloat $ a' + fromInteger b'
(MyFloat a', MyFloat b') -> MyFloat $ a' + b'

(*) a b = case (a, b) of
(MyInt a', MyInt b') -> MyInt $ a' * b'
(MyInt a', MyFloat b') -> MyFloat $ fromInteger a' * b'
(MyFloat a', MyInt b') -> MyFloat $ a' * fromInteger b'
(MyFloat a', MyFloat b') -> MyFloat $ a' * b'

fromInteger = MyInt

如您所见,abssignumnegate 除了底层操作不同外,它们是相同的。 (+)(*)也是如此。我怎样才能分解出这种重复的逻辑?

尝试的解决方案

myNumberMonoOp :: (a -> a) -> (MyNumber -> MyNumber)
myNumberMonoOp op = \case
MyInt val -> MyInt $ op val
MyFloat val -> MyFloat $ op val

myNumberBinOp :: (a -> a -> a) -> (MyNumber -> MyNumber -> MyNumber)
myNumberBinOp op a b = case (a, b) of
(MyInt a', MyInt b') -> MyInt $ a' `op` b'
(MyInt a', MyFloat b') -> MyFloat $ fromInteger a' `op` b'
(MyFloat a', MyInt b') -> MyFloat $ a' `op` fromInteger b'
(MyFloat a', MyFloat b') -> MyFloat $ a' `op` b'

instance Num MyNumber where
abs = myNumberMonoOp abs
signum = myNumberMonoOp signum
negate = myNumberMonoOp negate
(+) = myNumberBinOp (+)
(*) = myNumberBinOp (*)

这不是类型检查:

/path/to/Main.hs:44:27:error:
• Couldn't match expected type ‘a’ with actual type ‘Integer’
‘a’ is a rigid type variable bound by
the type signature for:
myNumberMonoOp :: forall a. (a -> a) -> MyNumber -> MyNumber
at src/Main.hs:42:1-52
• In the first argument of ‘op’, namely ‘val’
In the second argument of ‘($)’, namely ‘op val’
In the expression: MyInt $ op val
• Relevant bindings include
op :: a -> a (bound at src/Main.hs:43:16)
myNumberMonoOp :: (a -> a) -> MyNumber -> MyNumber
(bound at src/Main.hs:43:1)
|
44 | MyInt val -> MyInt $ op val
| ^^^

我(想我)明白为什么这是不允许的:如果是的话,op 可能不会为 IntegerFloat 定义,这显然是一个问题。但是,我仍然没有看到解决方案。有没有办法做到这一点?我想知道我是否需要根据类型类重写我的类型系统以避免这种重复。

最佳答案

由于 MyIntMyFloat 情况下的 abs 不相同,因此该解决方案将不起作用。实际上,MyIntabs 的类型是 abs::Int -> Int,而 MyFloat 的类型是输入 abs::Float -> Float

您可以创建一个与两个函数一起工作的函数,一个用于 Int,一个用于 Float:

mapNumber :: (<b>Int -> Int</b>) -> (<b>Float -> Float</b>) -> MyNumber -> MyNumber
mapNumber f g = go
where go (MyInt x) = MyInt (f x)
go (MyFloat x) = MyFloat (g x)

然后将其实现为:

instance Num MyNumber where
abs = <b>mapNumber</b> abs abs
signum = <b>mapNumber</b> signum signum
negate = <b>mapNumber</b> negate negate
# …

对于双参数操作,我们做类似的事情:

mapNumber2 :: (<b>Int -> Int -> Int</b>) -> (<b>Float -> Float -> Float</b>) -> MyNumber -> MyNumber
mapNumber2 f g = go
where go (MyInt x) (MyInt y) = MyInt (f x y)
go x y = g (go' x) (go' y)
go' (MyInt x) = fromIntegral x
go' (MyFloat x) = x

另一种选择是在此处使用 RankNTypes 语言扩展:

{-# LANGUAGE RankNTypes #-}

mapNumber :: (<b>forall a. Num a => a -> a</b>) -> MyNumber -> MyNumber
mapNumber f = go
where go (MyInt x) = MyInt (f x)
go (MyFloat x) = MyFloat (f x)

然后你可以用:

instance Num MyNumber where
abs = <b>mapNumber</b> abs
signum = <b>mapNumber</b> signum
negate = <b>mapNumber</b> negate
# …

关于haskell - 消除相似求和类型方法之间的代码重复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66713390/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com