约束签名中的构造函数

时间:2012-05-27 06:37:04

标签: haskell

所以,我正在进行一个有意义的有趣实验,而且我正在碰壁。我试图定义一个数据类型,它可以是原语,也可以是从一个构造函数转换到另一个构造函数的函数。

data WeaponPart =
    WInt Int |
    WHash (Map.Map String Int) |
    WNull |
    WTrans (WeaponPart -> WeaponPart)

instance Show WeaponPart where
    show (WInt x) = "WInt " ++ (show x)
    show (WHash x) = "WHash " ++ (show x)
    show (WTrans _) = "WTrans"
    show WNull = "WNull"

cold :: WeaponPart -> WeaponPart
cold (WInt x) = WHash (Map.singleton "frost" x)
cold (WHash x) = WHash $ Map.insertWith (+) "frost" 5 x
cold (WTrans x) = cold $ x (WInt 5)
cold (WNull) = cold $ (WInt 5)

ofTheAbyss :: WeaponPart -> WeaponPart
ofTheAbyss (WTrans x) = x (WTrans x)

问题是ofTheAbyss的签名允许任何WeaponPart作为参数,而我只想允许WTrans构造的参数。您可以看到我只为该案例编写了模式匹配。

我尝试过与GADT合作,但我担心这是一个兔子洞。永远不会让他们做我想做的事。有没有人有任何想法如何只能强制执行TheAbyss中的WTrans参数?或者我完全错过了什么。

感谢。

最佳, 埃里克

3 个答案:

答案 0 :(得分:10)

可以用GADT做这件事。从我这里来判断是什么结果是兔子洞,但让我至少显示配方。我正在使用新的PolyKinds扩展程序,但您可以使用较少的扩展程序。

首先,确定您需要的内容,并定义这些类型的数据类型。

data Sort = Base | Compound

接下来,定义按其排序索引的数据。这就像构建一种小型语言。

data WeaponPart :: Sort -> * where
  WInt    :: Int ->                                   WeaponPart Base
  WHash   :: Map.Map String Int ->                    WeaponPart Base
  WNull   ::                                          WeaponPart Base
  WTrans  :: (Some WeaponPart -> Some WeaponPart) ->  WeaponPart Compound

您可以通过存在量化来表示“任何种类的数据”,如下所示:

data Some p where
  Wit :: p x -> Some p

请注意x没有逃脱,但我们仍然可以检查x'满足'p的'证据'。请注意,Some必须是data类型,而不是newtype作为存在newtype的GHC对象。

您现在可以自由编写Sort - 通用操作。如果您有通用输入,则可以使用多态,有效地将Some p -> ...括起为forall x. p x -> ...

instance Show (WeaponPart x) where
  show (WInt x)    = "WInt " ++ (show x)
  show (WHash x)   = "WHash " ++ (show x)
  show (WTrans _)  = "WTrans"
  show WNull       = "WNull"

Sort需要存在性 - 通用输出:这里我用它来输入和输出。

cold :: Some WeaponPart -> Some WeaponPart
cold (Wit (WInt x))    = Wit (WHash (Map.singleton "frost" x))
cold (Wit (WHash x))   = Wit (WHash $ Map.insertWith (+) "frost" 5 x)
cold (Wit (WTrans x))  = cold $ x (Wit (WInt 5))
cold (Wit WNull)       = cold $ Wit (WInt 5)

我不得不偶尔添加关于这个地方的Wit,但它是同一个程序。

与此同时,我们现在可以写

ofTheAbyss :: WeaponPart Compound -> Some WeaponPart
ofTheAbyss (WTrans x) = x (Wit (WTrans x))

因此使用嵌入式系统并不可怕。有时需要付出代价:如果您希望嵌入式语言具有子分类,您可能会发现只是为了更改某些数据类型的索引而进行额外的计算,而对数据本身没有任何影响。如果你不需要再分类,额外的纪律通常可以成为真正的朋友。

答案 1 :(得分:3)

这是另一种可能的解决方案:将数据类型拆分为两个。我使用了与其他答案一致的名称,以便于查看相似之处。

data WeaponPartBase
    = WInt Int
    | WHash (Map.Map String Int)
    | WNull

data WeaponPartCompound = WTrans (WeaponPart -> WeaponPart)
data WeaponPart = Base WeaponPartBase | Compound WeaponPartCompound

cold :: WeaponPart -> WeaponPart
cold (Base (WInt x)) = Base (WHash (Map.singleton "frost" x))
cold (Base (WHash x)) = Base (WHash $ Map.insertWith (+) "frost" 5 x)
cold (Base WNull) = cold (Base (WInt 5))
cold (Compound (WTrans x)) = cold (x (Base (WInt 5))

ofTheAbyss :: WeaponPartCompound -> WeaponPart
ofTheAbyss (WTrans x) = x (WCompound (WTrans x))

通过为基本事物声明一个类,可以使这更方便:

class Basic a where
    wint :: Int -> a
    whash :: Map.Map String Int -> a
    wnull :: a

class Compounded a where
    wtrans :: (WeaponPart -> WeaponPart) -> a

instance Basic WeaponPartBase where
    wint = WInt
    whash = WHash
    wnull = WNull

instance Basic WeaponPart where
    wint = Base . wint
    whash = Base . whash
    wnull = Base wnull

instance Compounded WeaponPartCompound where
    wtrans = WTrans

instance Compounded WeaponPartCompound where
    wtrans = Compound . wtrans

以便例如coldofTheAbyss看起来像这样:

cold' (Base (WInt x)) = whash (Map.singleton "frost" x)
cold' (Base (WHash x)) = whash $ Map.insertWith (+) "frost" 5 x
cold' (Base WNull) = cold' (wint 5)
cold' (Compound (WTrans x)) = cold' (x (wint 5))

ofTheAbyss' (WTrans x) = x (wtrans x)

答案 2 :(得分:1)

你试图不是按类型约束你的函数,而是通过构造函数约束你的函数。这不是一件可行的事情。

事实上,它不应该是一个可行的事情 - 如果你正在编写另一个函数,并且你有一些未知的WeaponPart,你必须能够将它传递给ofTheAbyss或不 - 那有必要进行类型检查。

我能想到的两个选择是:

a)给ofTheAbyss类型(WeaponPart -> WeaponPart) -> WeaponPart,“解包”构造函数。

b)让ofTheAbyss在任何其他构造函数上给出运行时错误。

 ofTheAbyss :: WeaponPart -> WeaponPart
 ofTheAbyss (WTrans x) = x (WTrans x)
 ofTheAbyss _ = error "Illegal argument to ofTheAbyss was not a WTrans"