在Haskell中打开,输入安全且易于扩展的组件结构?

时间:2017-06-29 14:33:17

标签: haskell recursion functional-programming type-safety type-level-computation

我试图在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]

1 个答案:

答案 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包装器,但它很好。