Is there any recommended way to use typeclasses to emulate OCaml-like parametrized modules?
For an instance, I need the module that implements the complex generic computation, that may be parmetrized with different misc. types, functions, etc. To be more specific, let it be kMeans implementation that could be parametrized with different types of values, vector types (list, unboxed vector, vector, tuple, etc), and distance calculation strategy.
For convenience, to avoid crazy amount of intermediate types, I want to have this computation polymorphic by DataSet class, that contains all required interfaces. I also tried to use TypeFamilies to avoid a lot of typeclass parameters (that cause problems as well):
{-# 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)
This code somehow compiles and work, but it's pretty ugly and hacky.
The kMeans should be parametrized by value type (number, float point number, anything), box type (vector,list,unboxed vector, tuple may be) and distance calculation strategy.
There are also types for Observation (that's the type of sample provided by user, there should be a lot of them, measurements that contained in each observation).
So the problems are:
1) If the function does not contains the parametric types in it's signature, types will not be deduced
2) Still no idea, how to declare typeclass WithDistance to have different instances for different distance type (Euclid, Kullback, anything else via phantom types).
Right now WithDistance just polymorphic by box type and value type, so if we need different strategies, we may only put them in different modules and import the required module. But this is a hack and non-typed approach, right?
All of this may be done pretty easy in OCaml with is't modules. What the proper approach to implement such things in Haskell?
Typeclasses with TypeFamilies somehow look similar to parametric modules, but they work different. I really need something like that.
NullaryTypeClasses
extension. I imagine that this would be orphan instance trouble waiting to happen though. – David YoungTBoxType
? You don't have any functions that will return aTBoxType
in eitherWithDistance
orDataSet
. – CirdecData
types and polymorphism instead of typeclass
es. This frees you to e.g. easily select the distance function you want by passing it to the function. If type classes are needed and a type could have multiple interpretations, theinstance
is usually selected by usingnewtype
s. For example, theMonoid
class has two obvious implementations for integers, sonewtype
s are introduced to discern betweenSum
s andProduct
s. hackage.haskell.org/package/base-4.7.0.1/docs/… – Cirdec