对数据类型定义的限制

时间:2014-10-16 17:36:46

标签: haskell

对于type Entity = ([Feature], Body)Feature的意思,我有一个类型同义词BodyEntity类型的对象将组合在一起:

type Bunch = [Entity]

并且对于使用Bunch的算法至关重要的假设是,同一群中的任何两个实体具有相同数量的特征。

如果我要用OOP语言实现这个约束,我会将相应的检查添加到封装实体添加的方法中。 在Haskell中有更好的方法吗?优选地,在定义级别上。 (如果Entity的定义也需要更改,没问题。)

2 个答案:

答案 0 :(得分:2)

使用类型级长度注释

所以这就是这笔交易。 Haskell确实有type-level natural numbers,您可以使用"幻像类型"来注释类型。无论如何,类型将如下所示:

data Z
data S n
data LAList x len = LAList [x] -- length-annotated list

然后您可以为方便起见添加一些构造函数:

lalist1 :: x -> LAList x (S Z)
lalist1 x = LAList [x]
lalist2 :: x -> x -> LAList x (S (S Z))
lalist2 x y = LAList [x, y]
-- ...

然后你有了更通用的方法:

(~:) :: x -> LAList x n -> LAList x (S n)
x ~: LAList xs = LAList (x : xs)
infixr 5 ~:

nil :: LAList x Z
nil = LAList []

lahead :: LAList x (S n) -> x
lahead (LAList xs) = head xs

latail :: LAList x (S n) -> LAList x n
latail (LAList xs) = tail xs

但是List定义本身并没有任何,因为它很复杂。您可能对Data.FixedList包感兴趣,因为它也有一些不同的方法。基本上每种方法都会开始看起来有些奇怪,有些数据类型没有构造函数,但是稍微开始看起来很正常。

可能也可以获取类型类,以便上面的所有lalist1lalist2运算符都可以替换为

class FixedLength t where
    la :: t x -> LAList x n

但是您可能需要-XTypeSynonymInstances标志来执行此操作,因为您想要执行类似

的操作
type Pair x = (x, x)
instance FixedLength Pair where
    la :: Pair x -> LAList [x] (S (S Z))
    la (a, b) = LAList [a, b]

(当你从(a, b)转到Pair a时,这是一种不匹配。)

使用运行时检查

您可以非常轻松地采用不同的方法并将所有这些封装为运行时错误或在代码中明确建模错误:

-- this may change if you change your definition of the Bunch type
features :: Entity -> [Feature]
features = fst 

-- we also assume a runBunch :: [Entity] -> Something function 
-- that you're trying to run on this Bunch.

allTheSame :: (Eq x) => [x] -> Bool
allTheSame (x : xs) = all (x ==) xs
allTheSame [] = True

permissiveBunch :: [Entity] -> Maybe Something
permissiveBunch es
  | allTheSame (map (length . features) es) = Just (runBunch es)
  | otherwise = Nothing

strictBunch :: [Entity] -> Something
strictBunch es 
  | allTheSame (map (length . features) es) = runBunch es
  | otherwise = error ("runBunch requires all feature lists to be the same length; saw instead " ++ show (map (length . features) es))

然后您的runBunch可以假设所有长度都相同,并在上面明确检查。如果你需要将功能配对在一起,你可以使用Prelude中的zip :: [a] -> [b] -> [(a, b)]功能来解决模式匹配的怪异问题。 (由于runBunch' (x:xs) (y:ys)runBunch' [] []的模式匹配,此处的目标将是算法中的错误,但随后Haskell警告您在匹配中没有考虑2种模式。 )

使用元组和类型

最后一种方法是在两者之间进行折衷(但是制作相当不错的Haskell代码)涉及使实体参数化所有特征:

type Entity x = (x, Body)

然后包括一个可以将不同长度的不同实体压缩在一起的函数:

class ZippableFeatures z where
    fzip :: z -> z -> [(Feature, Feature)]

instance ZippableFeatures () where
    fzip () () = []

instance ZippableFeatures Feature where
    fzip f1 f2 = [(f1, f2)]

instance ZippableFeatures (Feature, Feature) where
    fzip (a1, a2) (b1, b2) = [(a1, b1), (a2, b2)]

然后你可以使用元组作为你的功能列表,只要它们不会超过最大元组长度(我的GHC为15)。当然,如果你大于那个,你总是可以定义自己的数据类型,但它不会像带注释的列表一样通用。

如果您这样做,runBunch的类型签名将如下所示:

 runBunch :: (ZippableFeatures z) => [Entity z] -> Something

当你在功能数量错误的东西上运行时,你会遇到编译器错误,它无法将类型(a,b)与(a,b,c)统一起来。

答案 1 :(得分:2)

有各种方法来强制执行这样的长度限制;这是一个:

{-# LANGUAGE DataKinds, KindSignatures, GADTs, TypeFamilies #-}
import Prelude hiding (foldr)
import Data.Foldable
import Data.Monoid
import Data.Traversable
import Control.Applicative

data Feature  -- Whatever that really is

data Body  -- Whatever that really is

data Nat = Z | S Nat  -- Natural numbers

type family Plus (m::Nat) (n::Nat) where  -- Type level natural number addition
  Plus Z n = n
  Plus (S m) n = S (Plus m n)

data LList (n :: Nat) a where  -- Lists tagged with their length at the type level
  Nil :: LList Z a
  Cons :: a -> LList n a -> LList (S n) a

这些名单上的一些功能:

llHead :: LList (S n) a -> a
llHead (Cons x _) = x

llTail :: LList (S n) a -> LList n a
llTail (Cons _ xs) = xs

llAppend :: LList m a -> LList n a -> LList (Plus m n) a
llAppend Nil ys = ys
llAppend (Cons x xs) ys = Cons x (llAppend xs ys)

data Entity n = Entity (LList n Feature) Body

data Bunch where
   Bunch :: [Entity n] -> Bunch

有些情况:

instance Functor (LList n) where
   fmap f Nil = Nil
   fmap f (Cons x xs) = Cons (f x) (fmap f xs)

instance Foldable (LList n) where
   foldMap f Nil = mempty
   foldMap f (Cons x xs) = f x `mappend` foldMap f xs

instance Traversable (LList n) where
   traverse f Nil = pure Nil
   traverse f (Cons x xs) = Cons <$> f x <*> traverse f xs

等等。请注意,n定义中的Bunch存在。它可以是任何东西,它实际上不会影响类型 - 所有束都具有相同的类型。这限制了您在一定程度上可以对束进行的操作。或者,您可以使用其功能列表的长度标记该束。这完全取决于你最终需要做什么