1

我在编写一个简单的函数而没有太多重复自己时遇到问题,下面是一个简化的示例。我正在尝试编写的真正程序是来自 python 的 BI 服务器的内存数据库端口。实际上,有更多不同的类型(大约 8 种)和更多的逻辑,这主要可以表示为对多态类型(如 Vector a)进行操作的函数,但仍然有一些逻辑必须处理不同类型的值。

由于效率原因,单独包装每个值(使用 [(Int, WrappedValue)] 类型)不是一个选项 - 在实际代码中,我使用的是未装箱的向量。

type Vector a = [(Int, a)] -- always sorted by fst

data WrappedVector = -- in fact there are 8 of them
      FloatVector (Vector Float)
    | IntVector (Vector Int)
    deriving (Eq, Show)

query :: [WrappedVector] -> [WrappedVector] -- equal length
query vectors = map (filterIndexW commonIndices) vectors
    where
        commonIndices = intersection [mapFstW vector | vector <- vectors]

intersection :: [[Int]] -> [Int]
intersection = head -- dummy impl. (intersection of sorted vectors)

filterIndex :: Eq a => [Int] -> Vector a -> Vector a
filterIndex indices vector = -- sample inefficient implementation
    filter (\(idx, _) -> idx `elem` indices) vector

mapFst :: Vector a -> [Int]
mapFst = map fst

-- idealy I whould stop here, but I must write repeat for all possible types
-- and kinds of wrapped containers and function this:

filterIndexW :: [Int] -> WrappedVector -> WrappedVector
filterIndexW indices vw = case vw of
    FloatVector v -> FloatVector $ filterIndex indices v
    IntVector   v -> IntVector $ filterIndex indices v

mapFstW :: WrappedVector -> [Int]
mapFstW vw = case vw of
    FloatVector v -> map fst v
    IntVector   v -> map fst v

-- sample usage of query
main = putStrLn $ show $ query [FloatVector [(1, 12), (2, -2)],
                                IntVector   [(2, 17), (3, -10)]]

如何在不像 mapFstW 和 filterIndexW 函数那样进行包装和展开的情况下表达这样的代码?

4

2 回答 2

2

如果您愿意使用一些编译器扩展,ExistentialQuantification 可以很好地解决您的问题。

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE StandaloneDeriving #-}
module VectorTest where

type PrimVector a = [(Int, a)]

data Vector = forall a . Show a => Vector (PrimVector a)

deriving instance Show Vector

query :: [Vector] -> [Vector] -- equal length
query vectors = map (filterIndex commonIndices) vectors
    where
        commonIndices = intersection [mapFst vector | vector <- vectors]

intersection :: [[Int]] -> [Int]
intersection = head -- dummy impl. (intersection of sorted vectors)

filterIndex :: [Int] -> Vector -> Vector
filterIndex indices (Vector vector) = -- sample inefficient implementation
    Vector $ filter (\(idx, _) -> idx `elem` indices) vector

mapFst :: Vector -> [Int]
mapFst (Vector l) = map fst l

-- sample usage of query
main = putStrLn $ show $ query [Vector [(1, 12), (2, -2)],
                                Vector [(2, 17), (3, -10)]]

如果您为 Vector 编写手动 Show 实例,则可以删除 StandaloneDeriving 要求,例如

instance Show Vector where
    show (Vector v) = show v
于 2013-10-02T19:10:05.930 回答
1

包装单一类型而不影响性能的标准选项是

{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- so we can derive Num
newtype MyInt = My Int deriving (Eq,Ord,Show,Num)
newtype AType a = An a deriving (Show, Eq)

因为它仅在类型级别产生差异 - 数据表示是相同的,因为它们都被编译掉了。您甚至可以指定未装箱的值,但是……这在这里对您没有帮助,因为您要包装多种类型。

真正的问题是您试图用静态类型的语言来表示动态类型的解决方案。动态类型的性能必然会受到影响,动态类型在动态语言中对您隐藏,但在标记中明确显示。

你有两个解决方案:

  1. 接受动态类型涉及对静态类型的额外运行时检查,并接受丑陋的生活。
  2. 拒绝动态类型的需要,接受多态类型整理所有代码并将类型检查移动到编译时间和数据采集。

我觉得 2 是迄今为止最好的解决方案,您应该放弃尝试列出您想要使用的所有类型的程序,而不是编程使用任何类型。它整洁、清晰、高效。您检查有效性并处理一次,然后停止担心。

于 2013-10-02T18:58:39.467 回答