2014-10-12 56 views
4

有沒有推薦的方法來使用類型類來模擬類OCaml的參數化模塊?Haskell中的OCaml仿函數(參數化模塊)

對於一個實例,我需要實現複雜的泛型計算的模塊,該模塊可能會使用不同的 misc進行parmetrized。類型,函數等。更具體地說,讓它成爲可以用不同的 類型的值,矢量類型(列表,未裝箱的矢量,矢量,元組等), 和距離計算策略參數化的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) 

此代碼編譯莫名其妙和工作,但它是很醜陋和哈克。

kMeans應該通過值類型(數字,浮點數,任何東西)進行參數化,框類型(向量,列表,未裝箱的向量,元組可以)和距離計算策略。

也有類型的觀察(這是用戶提供的樣品類型, 應該有很多,包含在每個觀察測量)。

所以問題是:

1)如果函數不包含在它的標誌性的參數類型, 類型將無法被推斷

2)仍然沒有想法,如何類型類WithDistance申報對於不同的距離類型(Euclid,Kullback,其他任何通過幻像類型)有不同的實例 。

目前WithDistance只是多種按框型和值類型,所以如果我們需要 不同的策略,我們可能只會把它們放在不同的模塊中,並導入所需的 模塊。但這是一種黑客和非類型的方法,對嗎?

所有這些在OCaml中都可以用非模塊來完成。在Haskell中實現這些東西的正確方法 是什麼?

帶TypeFamilies的Typeclasses看起來與參數模塊類似,但它們的工作原理與 不同。我真的需要那樣的東西。

+0

我不知道它是否真的被推薦,但GHC 7.8和更高版本允許使用'NullaryTypeClasses'擴展名的空類型類。我想,這將是等待發生的孤兒實例麻煩。 – 2014-10-12 06:37:08

+0

你打算如何使用'TBoxType'?您沒有任何函數會在'WithDistance'或'DataSet'中返回'TBoxType'。 – Cirdec 2014-10-12 07:44:39

+1

一般來說,在Haskell中,這類問題是通過普通的舊數據類型和多態而不是類型類來實現的。這可以讓你免費通過傳遞給函數輕鬆選擇您想要的距離函數。如果需要類型類並且類型可以有多個解釋,那麼通常使用'newtype'來選擇'instance'。例如,「Monoid」類有兩個明顯的整數實現,所以引入'newtype'來區分'Sum'和'Product'。 http://hackage.haskell.org/package/base-4.7.0.1/docs/Data-Monoid.html#t:Sum – Cirdec 2014-10-12 07:48:34

回答

3

Haskell確實沒有在ML模塊系統中找到有用的功能。
有不斷努力擴大Haskell的模塊系統:http://plv.mpi-sws.org/backpack/

但我認爲你還可以得到一點沒有這些ML模塊。 您的設計遵循God class反模式,這就是爲什麼它是反模塊化的原因。

只有每個類型只能有一個該類的單個實例時,Type類纔有用。例如。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' 

等。

建議的方法是推廣算法的各個部分,並用小松散耦合類型類(必要時)約束這些部分。
在單一的單體類型中收集所有內容是一種不好的風格。

+0

三件東西 - 觀察,距離函數和盒子類型的對象離神對象很遠。 如果你的情況我們最終將得到DataSet類的一堆參數,我們將面臨同樣的問題---對於不接受數據集但只接受它的部分的輔助函數,不會推導出類型。 – voidlizard 2014-10-13 04:44:35

+0

另外---矢量(盒子類型)它是kMeans實現的內部細節,是揭示它的唯一原因---我們需要觀察之間的距離函數。但不同類型的容器甚至不具有用於摺疊的通用界面,而非盒裝矢量甚至不提供可摺疊的界面。這就是爲什麼在這裏WithDistance typeclass。 – voidlizard 2014-10-13 04:56:53

+0

但我認爲使用普通舊參數ADT和新類型 來實現特定實現(Vector,Unboxed Vector)的想法是合理的。 – voidlizard 2014-10-13 05:13:31