在Haskell中迭代自定义数据类型

时间:2017-09-05 12:18:17

标签: haskell

我有一个如下所示的自定义数据类型:

data Circle = Circle
        { radius                   :: Float
        , xPosition                :: Float
        , yPosition                :: Float
        }

我希望能够编写一个缩放函数,它可以取一个给定的圆并改变它的大小:

aCircle = Circle 1.5 1 1
scaleFn aCircle 10

此示例的所需输出(标度为10)将为:

Circle 15 10 10

如何创建一个函数,我可以迭代每个字段并将值乘以常量?在我的实际用例中,我需要一种方法来映射所有字段,因为它们有很多。

2 个答案:

答案 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 ff转换为适用于任何类型的函数:如果mkT f的参数是Float,则f应用,否则参数按原样返回。 新构造的通用函数称为“转换”:T中的mkT代表该转换。

然后,gmapT将转化mkT f应用于圈子的所有字段。请注意,这是一个包含的字段,例如,(Float, Bool)浮点数不受影响。使用everywhere代替gmapT递归深入。

请注意,我不是这种方法的忠实粉丝。如果由于任何原因您更改了字段的类型,则该更改不会触发类型错误,但gmapT (mkT ...)现在只会跳过该字段。

通用编程可能很方便,但有时候有点太多,因为类型错误可以在运行时静默转换为意外结果。小心使用。