打开,键入安全且易于扩展的Haskell组件结构



我试图在haskell中实现一个小而简单的开放类型组件。
目标是通过在编译时保留类型安全性的同时在其他模块中创建新组件来轻松允许功能和键入扩展。

我设法按组件类型实现了递归搜索功能(在CompFinder中找到(,并且在我尝试过的简单测试中效果很好。但是,我需要为非复合组件和多个实例编写CompFinder的琐碎实例,而唯一的区别是CompFinder CompMix实例的组件类型。

我知道是为自动代码生成而得出的,例如派生,但是如果有的话,我想要一个更简单的解决方案,如果可能的话,编译器在最坏的情况下进行代码生成。

所以问题是:如何修改此系统以简化新组件的写作?
或:我如何告诉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]

查看了一些通用的haskell后,我终于找到了一种比自动推导更容易的方法,以避免为Finder(以前是CompFinder(编写每个实例。通过这种方式,我们只需要为用户定义的结构类型定义Finder的实例。

从一些布拉格斯开始

{-# LANGUAGE MultiParamTypeClasses #-} -- allows functions at the type level
{-# LANGUAGE FlexibleContexts #-} -- allows using data types in typeclass declarations

然后定义一个简单的组件包装器。这样,我们将仅为所有简单组件编写一次Finder的实例。

data Component a = Comp a

然后定义Finder类,我们将使用无功能依赖项的多型型组件,因为我们想指定返回类型以获取所需的组件:

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) = []

我们可以使用简单的混合(2汤匙(来构建组件:

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]
  []

这也可以与嵌套混合物一起使用!唯一的缺点是使用组件包装器,但很好。

最新更新