对于type Entity = ([Feature], Body)
和Feature
的意思,我有一个类型同义词Body
。 Entity
类型的对象将组合在一起:
type Bunch = [Entity]
并且对于使用Bunch
的算法至关重要的假设是,同一群中的任何两个实体具有相同数量的特征。
如果我要用OOP语言实现这个约束,我会将相应的检查添加到封装实体添加的方法中。
在Haskell中有更好的方法吗?优选地,在定义级别上。 (如果Entity
的定义也需要更改,没问题。)
答案 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包感兴趣,因为它也有一些不同的方法。基本上每种方法都会开始看起来有些奇怪,有些数据类型没有构造函数,但是稍微开始看起来很正常。
您可能也可以获取类型类,以便上面的所有lalist1
,lalist2
运算符都可以替换为
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
是存在。它可以是任何东西,它实际上不会影响类型 - 所有束都具有相同的类型。这限制了您在一定程度上可以对束进行的操作。或者,您可以使用其功能列表的长度标记该束。这完全取决于你最终需要做什么做。