我试图在Haskell中实现一个小而简单的开放式组件结构 目标是通过在其他模块中创建新组件,同时在编译时保持类型安全性,轻松地允许函数和类型扩展。
我设法按组件类型实现递归搜索功能(在CompFinder
中查找),它在我尝试的简单测试中运行良好。但是,我需要为非复合组件和多个实例编写CompFinder
的普通实例,唯一的区别是CompFinder CompMix
实例的组件类型。
我知道Derive用于实例派生的自动代码生成但我希望有一个更简单的解决方案,如果有的话,编译器在最坏的情况下执行代码生成,如果可能的话。
所以问题是:如何修改这个系统以简化新组件的编写?
或者:我如何告诉GHC自动生成CompFinder实例,因为只有不同定义中的类型更改?
也许通过做这样的事情:
instance (Component a, CompFinder a T, Component b, CompFinder b T) => CompFinder (CompMix a b) T where
find (CompMix x y ) = (find x :: [T]) ++ (find y :: [T])
其中T表示类型参数
或者也许通过编写CompFinder,它可以自动适用于所有组件。
这是完整的代码:
--ghc 7.10
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
-- Defined in the library module
class Component a
class ComponentMix a where -- Allow recursive component aggregation
add :: b -> c -> a b c
class (Component a, Component b) => CompFinder a b where -- class that need a new instance for each new component type
find :: a -> [b]
find _ = []
-- Defined in the user module
data CompMix a b = CompMix a b deriving (Show, Eq)
instance ComponentMix CompMix where
add x y = ( CompMix x y )
instance (Component a, Component b) => Component (CompMix a b)
-- Two simple component types that can be defined in another module
data Position = Pos Int Int deriving (Show, Eq, Ord)
data Name = Name String deriving (Show, Eq)
instance Component Position
instance CompFinder Position Position where -- Trivial instance
find x = [x]
instance CompFinder Position Name -- Trivial instance
instance Component Name
instance CompFinder Name Name where -- Trivial instance
find x = [x]
instance CompFinder Name Position -- Trivial instance
instance (Component a, CompFinder a Name, Component b, CompFinder b Name) => CompFinder (CompMix a b) Name where
find (CompMix x y ) = (find x :: [Name]) ++ (find y :: [Name])
instance (Component a, CompFinder a Position, Component b, CompFinder b Position) => CompFinder (CompMix a b) Position where
find (CompMix x y ) = (find x :: [Position]) ++ (find y :: [Position])
main = print $ (find (CompMix (Name "Henri") (CompMix (Pos 1 2) (Name "Julie") ) ) :: [Position] ) -- Correctly outputs [Pos 1 2]
答案 0 :(得分:0)
在查看了一些Generic Haskell之后,我终于发现了一种比自动派生更简单的方法,以避免为Finder
(以前为CompFinder
)编写每个实例。
通过这种方式,我们只需要为用户定义的结构类型定义Finder
的实例。
从一些编译指示开始
{-# LANGUAGE MultiParamTypeClasses #-} -- allows functions at the type level
{-# LANGUAGE FlexibleContexts #-} -- allows using data types in typeclass declarations
然后定义一个简单的Component包装器。这样我们只会为所有简单组件为Finder编写一次实例。
data Component a = Comp a
然后定义Finder类,我们将使用没有Functional依赖项的MultiParamTypeClasses,因为我们想要指定返回类型以获得所需的组件:
class Finder s c where -- s is the structure and c the component
find :: s -> [c]
find _ = [] -- provides empty list on search fail
我们可以为包装器实现这一点,首先是实际组件和搜索组件匹配。
instance {-# OVERLAPABLE #-} Finder (Component a) (Component a) where
find (Comp o) = [Comp o]
如果组件不同,我们利用OVERLAPPABLE
pragma提供一个空列表:
instance Finder (Component a) (Component b) where
find (Comp o) = []
我们可以使用简单的Mix(2-Tuple)构建我们的组件,例如:
data Mix a b = Mix a b deriving (Show,Eq,Ord)
instance (Finder a (Component c), Finder b (Component c) ) => Finder (Mix a b) (Component c) where
find (Mix x y) = find x ++ (find y)
现在让我们定义一些组件:
data Velocity = Vel Int Int deriving (Show,Eq,Ord)
data Name = Name String deriving (Show,Eq,Ord)
最后:
myStruct = Mix (Comp $ Name "Julien") (Comp $ Vel 5 2)
-- we can use the return type to ask for a list of components of that type
Prelude> find myStruct :: [Component Name]
[Comp (Name "Julien")]
Prelude> find myStruct :: [Component Velocity]
[Comp (Vel 5 2)]
Prelude> find (Comp $ Name "Marie") :: [Component Velocity]
[]
这也适用于嵌套的Mix! 唯一的缺点是使用Component包装器,但它很好。