我有一个如下所示的自定义数据类型:
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
}
我希望能够编写一个缩放函数,它可以取一个给定的圆并改变它的大小:
aCircle = Circle 1.5 1 1
scaleFn aCircle 10
此示例的所需输出(标度为10)将为:
Circle 15 10 10
如何创建一个函数,我可以迭代每个字段并将值乘以常量?在我的实际用例中,我需要一种方法来映射所有字段,因为它们有很多。
答案 0 :(得分:2)
按因子缩放通常为vector space operation。您可以执行以下操作:
{-# LANGUAGE TypeFamilies, DeriveGeneric #-}
import Data.VectorSpace
import GHC.Generics (Generic)
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
} deriving (Generic, Show)
instance AdditiveGroup Circle
instance VectorSpace Circle where
type Scalar Circle = Float
main = print $ Circle 1.5 1 1 ^* 10
(结果:Circle {radius = 15.0, xPosition = 10.0, yPosition = 10.0}
)。
(需要vector-space >= 0.11
,其中只有added support for generic-derived instances。)
但是我应该注意Circle
因为不真的是一个很好的VectorSpace
实例:添加两个圆圈没有任何意义,并且按比例缩放负因子给出了一个虚假的半径。如果您的实际用例遵循实际的vector space axioms,则仅定义此类实例。
像Circle
这样的类型你真正想要的是diagrams
' Transformable
class。但我不认为有任何自动方法可以为此派生实例。事实上,由于diagrams
已经 - 不幸的是IMO - 从vector-space
切换到linear
,这样的事情在原则上变得相当困难。
答案 1 :(得分:2)
您可以使用“废弃样板”:
import Data.Generics
data Circle = Circle
{ radius :: Float
, xPosition :: Float
, yPosition :: Float
}
deriving (Show, Data)
circleModify :: (Float -> Float) -> Circle -> Circle
circleModify f = gmapT (mkT f)
直观地,上面mkT f
将f
转换为适用于任何类型的函数:如果mkT f
的参数是Float
,则f
应用,否则参数按原样返回。
新构造的通用函数称为“转换”:T
中的mkT
代表该转换。
然后,gmapT
将转化mkT f
应用于圈子的所有字段。请注意,这是一个包含的字段,例如,(Float, Bool)
浮点数不受影响。使用everywhere
代替gmapT
递归深入。
请注意,我不是这种方法的忠实粉丝。如果由于任何原因您更改了字段的类型,则该更改不会触发类型错误,但gmapT (mkT ...)
现在只会跳过该字段。
通用编程可能很方便,但有时候有点太多,因为类型错误可以在运行时静默转换为意外结果。小心使用。