所以,我正在进行一个有意义的有趣实验,而且我正在碰壁。我试图定义一个数据类型,它可以是原语,也可以是从一个构造函数转换到另一个构造函数的函数。
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参数?或者我完全错过了什么。
感谢。
最佳, 埃里克
答案 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
以便例如cold
和ofTheAbyss
看起来像这样:
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"