是否有任何推荐的方法来使用类型类来模拟类似OCaml的参数化模块?
对于一个实例,我需要实现该复合体的模块 通用计算,可以用不同的方式进行分解 杂项。类型,功能等。更具体地说,就是这样 kMeans实现可以用不同的参数化 值的类型,矢量类型(列表,未装箱的矢量,矢量,元组等), 和距离计算策略。
为方便起见,为了避免疯狂数量的中间类型,我想 这个计算多态由DataSet类,包含所有 必需的接口。我也尝试使用TypeFamilies来避免很多 类型参数(也会导致问题):
{-# Language MultiParamTypeClasses
, TypeFamilies
, FlexibleContexts
, FlexibleInstances
, EmptyDataDecls
, FunctionalDependencies
#-}
module Main where
import qualified Data.List as L
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Distances
-- contains instances for Euclid distance
-- import Distances.Euclid as E
-- contains instances for Kulback-Leibler "distance"
-- import Distances.Kullback as K
class ( Num (Elem c)
, Ord (TLabel c)
, WithDistance (TVect c) (Elem c)
, WithDistance (TBoxType c) (Elem c)
)
=> DataSet c where
type Elem c :: *
type TLabel c :: *
type TVect c :: * -> *
data TDistType c :: *
data TObservation c :: *
data TBoxType c :: * -> *
observations :: c -> [TObservation c]
measurements :: TObservation c -> [Elem c]
label :: TObservation c -> TLabel c
distance :: TBoxType c (Elem c) -> TBoxType c (Elem c) -> Elem c
distance = distance_
instance DataSet () where
type Elem () = Float
type TLabel () = Int
data TObservation () = TObservationUnit [Float]
data TDistType ()
type TVect () = V.Vector
data TBoxType () v = VectorBox (V.Vector v)
observations () = replicate 10 (TObservationUnit [0,0,0,0])
measurements (TObservationUnit xs) = xs
label (TObservationUnit _) = 111
kMeans :: ( Floating (Elem c)
, DataSet c
) => c
-> [TObservation c]
kMeans s = undefined -- here the implementation
where
labels = map label (observations s)
www = L.map (V.fromList.measurements) (observations s)
zzz = L.zipWith distance_ www www
wtf1 = L.foldl wtf2 0 (observations s)
wtf2 acc xs = acc + L.sum (measurements xs)
qq = V.fromList [1,2,3 :: Float]
l = distance (VectorBox qq) (VectorBox qq)
instance Floating a => WithDistance (TBoxType ()) a where
distance_ xs ys = undefined
instance Floating a => WithDistance V.Vector a where
distance_ xs ys = sqrt $ V.sum (V.zipWith (\x y -> (x+y)**2) xs ys)
这段代码以某种方式编译和工作,但它非常丑陋和hacky。
kMeans应该按值类型(数字,浮点数,任何东西)进行参数化, 框类型(向量,列表,未装箱的向量,可以是元组)和距离计算策略。
还有观察类型(用户提供的样本类型, 应该有很多,每次观察都包含测量值。
所以问题是:
1)如果该功能不包含其签名中的参数类型, 类型不会被推断
2)仍然不知道,如何声明类型类WithDistance具有不同的实例 对于不同的距离类型(Euclid,Kullback,其他任何通过幻像类型)。
现在WithDistance只是盒子类型和值类型的多态,所以如果我们需要的话 不同的策略,我们可能只将它们放在不同的模块中并导入所需的 模块。但这是一种黑客和非打字的方法,对吗?
所有这些都可以在OCaml中使用is&t; t模块完成。什么是正确的方法 在Haskell中实现这些东西?
具有TypeFamilies的类型类似于参数模块,但它们 工作不同。我真的需要这样的东西。
答案 0 :(得分:5)
Haskell确实缺乏* ML模块系统中的有用功能 目前正在努力扩展Haskell的模块系统:http://plv.mpi-sws.org/backpack/
但我认为如果没有这些ML模块,你可以得到更多。 您的设计遵循God class反模式,这就是反模块化的原因。
只有当每个类型只能包含该类的一个实例时,类型类才有用。例如。 DataSet ()
个实例已修复type TVect () = V.Vector
,您无法使用TVect = U.Vector
轻松创建类似的实例。
你需要从实现kMeans
函数开始,然后通过用类型变量替换具体类型并在需要时用类型约束那些类型变量来概括它。
这是一个小例子。首先,你有一些非一般的实现:
kMeans :: Int -> [(Double,Double)] -> [[(Double,Double)]]
kMeans k points = ...
然后通过距离计算策略对其进行概括:
kMeans
:: Int
-> ((Double,Double) -> (Double,Double) -> Double)
-> [(Double,Double)]
-> [[(Double,Double)]]
kMeans k distance points = ...
现在您可以按点类型对其进行推广,但这需要引入一个类,该类将捕获距离计算所使用的点的某些属性,例如:获取坐标列表:
kMeans
:: Point p
=> Int -> (p -> p -> Coord p) -> [p]
-> [[p]]
kMeans k distance points = ...
class Num (Coord p) => Point p where
type Coord p
coords :: p -> [Coord p]
euclidianDistance
:: (Point p, Floating (Coord p))
=> p -> p -> Coord p
euclidianDistance a b
= sum $ map (**2) $ zipWith (-) (coords a) (coords b)
现在,您可能希望通过使用向量替换列表来加快速度:
kMeans
:: (Point p, DataSet vec p)
=> Int -> (p -> p -> Coord p) -> vec p
-> [vec p]
kMeans k distance points = ...
class DataSet vec p where
map :: ...
foldl' :: ...
instance Unbox p => DataSet U.Vector p where
map = U.map
foldl' = U.foldl'
等等。
建议的方法是概括算法的各个部分,并用小松散耦合类型约束那些部分(如果需要)。
在单个整体类型类中收集所有内容是一种糟糕的风格。