2012-11-18 36 views
5

我有簡單的元組(例如從數據庫讀取),我不知道元素的數量和內容。例如。 (String, Int, Int)(String, Float, String, Int)操作「任意」元組

我想寫一個通用函數,它將採用各種元組並用字符串「NIL」替換所有數據。如果字符串「NIL」已經存在,它應該保持不變。

說回例如: ("something", 3, 4.788)應導致("something", "NIL", "NIL")

("something else", "Hello", "NIL", (4,6))應導致("something else", "NIL", "NIL", "NIL")

我明明不知道從哪裏開始,因爲它不會與元組做這是一個問題這是已知的。如果沒有Template Haskell,可以在這裏得到我想要的結果嗎?

+0

你想讓第一個項目獨立,但所有其他項目都是「NIL」? – AndrewC

+4

你確定元組是正確的數據類型嗎?有一些方法可以做到這一點,但如果您將數據轉化爲更好的類型,那麼這種方法就不那麼笨拙了。另外,你確定你想要第一個元素得到這樣的特殊待遇嗎? – shachaf

+0

使用像SYB這樣的泛型庫比Template Haskell更好 - 根據user5402的回答,你應該看看HList。 –

回答

8

這是可能的使用GHC.Generics,我想我會在這裏記錄它的完整性,雖然我不會推薦它在這裏的其他建議。

這個想法是將你的元組轉換成模式匹配的東西。典型的方式(我相信HList使用)是從一個n元組轉換爲嵌套元組:(,,,) - >(,(,(,)))

GHC.Generics通過將元組轉換爲產品:*:構造函數的嵌套應用程序做了類似的事情。 tofrom是將值轉換爲其通用表示的功能。元組字段通常由K1新類型表示,所以我們想要做的是通過元數據樹(M1)和產品(:*:)節點進行遞歸,直到找到K1葉節點(常量)並用一個元素替換它們的內容「NIL」字符串。

Rewrite類型函數描述了我們如何修改類型。 Rewrite (K1 i c) = K1 i String指出我們將用String替換每個值(c類型參數)。

給定一個小的測試應用程序:

y0 :: (String, Int, Double) 
y0 = ("something", 3, 4.788) 

y1 :: (String, String, String, (Int, Int)) 
y1 = ("something else", "Hello", "NIL", (4,6)) 

main :: IO() 
main = do 
    print (rewrite_ y0 :: (String, String, String)) 
    print (rewrite_ y1 :: (String, String, String, String)) 

我們可以使用一個通用的重寫產生:

 
*Main> :main 
("something","NIL","NIL") 
("something else","NIL","NIL","NIL") 

使用內置Generics功能和類型類做實際的轉型:

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE TypeOperators #-} 

import Data.Typeable 
import GHC.Generics 

rewrite_ 
    :: (Generic a, Generic b, Rewriter (Rep a), Rewrite (Rep a) ~ Rep b) 
    => a -> b 
rewrite_ = to . rewrite False . from 

class Rewriter f where 
    type Rewrite f :: * -> * 
    rewrite :: Bool -> f a -> (Rewrite f) a 

instance Rewriter f => Rewriter (M1 i c f) where 
    type Rewrite (M1 i c f) = M1 i c (Rewrite f) 
    rewrite x = M1 . rewrite x . unM1 

instance Typeable c => Rewriter (K1 i c) where 
    type Rewrite (K1 i c) = K1 i String 
    rewrite False (K1 x) | Just val <- cast x = K1 val 
    rewrite _ _ = K1 "NIL" 

instance (Rewriter a, Rewriter b) => Rewriter (a :*: b) where 
    type Rewrite (a :*: b) = Rewrite a :*: Rewrite b 
    rewrite x (a :*: b) = rewrite x a :*: rewrite True b 

而這個例子沒有使用的一些實例,他們會需要爲其它數據類型:

instance Rewriter U1 where 
    type Rewrite U1 = U1 
    rewrite _ U1 = U1 

instance (Rewriter a, Rewriter b) => Rewriter (a :+: b) where 
    type Rewrite (a :+: b) = Rewrite a :+: Rewrite b 
    rewrite x (L1 a) = L1 (rewrite x a) 
    rewrite x (R1 b) = R1 (rewrite x b) 

多一點努力Typeable約束可以從K1實例被刪除,不管是好是壞是值得商榷的,由於重疊/ UndecidableInstances。 GHC也不能推斷出結果類型,儘管它看起來應該能夠。無論如何,結果類型必須是正確的,否則您將很難看到錯誤消息。

+0

謝謝,我需要消化這個。問題是,雖然HList可能是我需要的,但我必須用()來表示。我曾經閱讀過Haskell,有些方法可以將這些表示方式更改爲您的喜好,但無法找回它。 –

+0

+1,我來到類似的解決方案,但與OverlappingInstances而不是Typeable –

+0

@JFritsch我已經添加了幾個評論。閱讀「GHC.Generics」教程也可能是一個好主意。 –

3

在haskell中,每一個元組都是另一種類型的,所以我不認爲你可以用任何簡單的方法寫一個沒有TH的函數來做到這一點。另外,GHC對允許的元組的最大大小施加限制。哈斯克爾標準只是說編譯器應該允許的元組ATLEAST高達15大小一樣

Prelude> let a = (1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0) 

<interactive>:2:9: 
    A 70-tuple is too large for GHC 
     (max size is 62) 
     Workaround: use nested tuples or define a data type 

所以我想,而不是使用元組,你應該嘗試使用一些其他的數據類型。

5

查看HListVinyl包作爲使用元組的替代方法。

乙烯需要GHC 7.6,並且預計很快就會有更新(根據最新的Haskell社區活動報告).HList尤其看起來非常適合代表SQL查詢的結果。

關於HList:

HList是一個全面的,通用的Haskell庫類型異構收藏,包括可擴展的多態記錄和變種。 HList類似於標準列表庫,提供了各種構造,查找,過濾和迭代原語。與常規列表相比,異構列表的元素不必具有相同的類型。 HList允許用戶制定靜態可檢查的約束條件:例如,集合中沒有兩個元素可能具有相同的類型(因此元素可以按其類型明確地索引)。

...

在2012年10月版HList庫標誌着顯著重寫採取由GHC 7.4+提供的票友類型的優勢。現在,HList依賴於類型級布爾值,自然數和列表以及類型多態性。一些操作被實現爲類型函數。另一個值得注意的補充是展示異構列表。許多操作(投影,分割)現在都在展開。這樣的重構把更多的計算轉移到了類型級別上,沒有運行時間的開銷。

4

有人在評論中提到了這一點,但也許你應該使用列表而不是元組。您可以使用:

data MyType = S String | D Double 

someData :: [MyType] 

然後,你可以使用列表轉換一個簡單map

convert :: MyType -> String 
convert (S str) = str 
convert _  = "NIL" 

convertList :: [MyType] -> [String] 
convertList = map convert 

我也想不明白你怎麼會不知道你的價值源的元組大小。你應該澄清一點。