我正在尋找一種方法,將一個串列轉化為一個n-tuple,其中n個建構式中的每一個都是不相連的聯合體。標準庫專門為Eithers定義了一個類似的函式:
partitionEithers :: [Either a b] -> ([a], [b])
我正在尋找解決具有以下要求的廣義問題的技術:
我正在尋找解決具有以下要求的廣義問題的技術。
- 方便書寫 盡可能少地使用模板一次性處理串列。
- 資料型別生成器、元編程、現有庫等都是允許的 。
示例
下面是一個規范的例子,其中有兩個建議的解決方案:。partitionSum :: [MySum] -> ([A], [B], [C], [D] )
data MySum
= CaseA A
| CaseB B
| CaseC C
| CaseD D
data A = A deriving Show
data B = B deriving Show
data C = C deriving Show
data D = D deriving Show
--期望"([A,A],[B,B,B],[],[D])"
test :: IO ()
test = print .partitionSum$
[CaseD D, CaseB B, CaseA A。CaseA A, CaseB B, CaseB B ]
第一次嘗試。n串列理解,遍歷串列n次。
partitionSum1 :: [MySum] -> ([A], [B], [C], [D] )
partitionSum1 xs =
( [a | CaseA a <- xs] 。
, [b | CaseB b <- xs] 。
, [c | CaseC c <- xs] 。
, [d | CaseD d <- xs] 。
)
第二次嘗試:對輸入串列進行一次遍歷。我必須手動將狀態穿透折疊,這使得解決方案有點重復,寫起來也很煩人。
第二種嘗試:對輸入串列進行單次遍歷。
partitionSum2 :: [MySum] -> ([A], [B], [C], [D] )
partitionSum2 = foldr f ([], [], [], [] )
where
f x (as, bs, cs, ds) =
case x of
CaseA a -> (a : as, bs, cs, ds)
CaseB b -> (as, b : bs, cs, ds)
CaseC c -> (as, bs, c : cs, ds)
CaseD d -> (as, bs, cs, d : ds)
uj5u.com熱心網友回復:
在看到foldr f ([], [], [], [])時,我想到的是定義一個單數,其中的nil情況是mempty
{-# DerivingVia #-}
..
import GHC.Generics (Generically(.), .)
type Classify ::
type Classify = C [A] [B] [C] [D]/span>
deriving
存貨 Generic
deriving (Semigroup, Monoid)
通過Generically Classify
-- mempty = C [] [] []/span>
-- C as bs cs ds <> C as1 bs1 cd1 ds1 = C (as as1) (bs bs1) (cs cs1) (ds ds1)
Generically將來會從GHC.Generics匯出。它將Classify定義為一個半群和單體,通過通用的點狀提升。
有了這個,你只需要一個分類器函式,將MySum分類為Classify,你可以用foldMap來定義partition。
classify :: MySum -> 分類
classify = case
SumA a -> C [a] [] [] 。
SumB b -> C [] [b] [] [] 。
SumC c -> C [] [c] [] 。
SumD d -> C [] [] [d)
partition :: Foldable f => f MySum -> Classify :.
partition = foldMap classify
uj5u.com熱心網友回復:
由于你的函式是一個從和到積的轉換,有一個相當簡單的實作,使用generics-sop。這是一個庫,它用更多的專門型別來增強 GHC 的泛型,使對 algebriac 型別(即乘積之和)的歸納更加簡單。
首先,一個前奏:
{-# LANGUAGE DeriveGeneric, StandaloneDeriving #-}
import Generics.SOP hiding ((:.:)
import qualified GHC.Generics as GHC
import GHC.Generics ((:.:)(..))
partitionSum :: (Generic t) => [t] -> NP ([] :. : NP I) (Code t)
這是你要寫的方法。讓我們研究一下它的型別。
- 單個引數是一個通用型別的串列。相當直接。請注意,
Generic是來自generics-sop,而不是來自GHC 。
- 回傳的值是一個n-ary乘積(n-tuple),其中每個元素都是由
NP I組成的串列(本身是一個n-ary乘積,因為一般來說,代數資料型別構造器可能有一個以上的欄位) 。
Code t是t的積和型別表示。它是一個型別的串列。例如,Code (Either a b) ~ '[ '[a], '[b] ]/code>。t的通用值表示是SOP I (Code t)--"代碼 "上的乘積之和。
為了實作這一點,我們可以將每個t轉換為其通用表示法,然后對所得到的串列進行折疊:
partitionSum = partitionSumGeneric . map from
partitionSumGeneric :: SListI xss => [SOP I xss] -> NP ([] :. : NP I) xss
partitionSumGeneric = foldr ((SOP x) -> classifyGeneric x) emptyClassifier
partitionSumGeneric與partitionSum基本相同,但對數值的通用表示進行操作。
現在是有趣的部分。讓我們從我們的折疊的基本情況開始。這應該在每個位置都包含空串列。generics-sop提供了一個方便的機制,用于生成一個在每個位置都有統一值的產品型別:
emptyClassifier :: SListI xs => NP ([]:. : NP I) xs
emptyClassifier = hpure (Comp1 [] )
遞回情況如下:如果值在索引k處有標簽,則將該值加入累加器中索引k處的串列。我們可以在和的型別(現在是通用的,所以一個NS (NP I) xs型別的值--產品之和)和累加器上同時進行遞回。
classifyGeneric :: NS (NP I) xss -> NP ( [] :. :: NP I) xss -> NP ([] :.: NP I) xss
classifyGeneric (Z x) (Comp1 l :* ls) = (Comp1 $ x : l) :* ls
classifyGeneric (S xs) ( l :* ls) = l :* classifyGeneric xs ls
你的例子添加了一些資料,使其更有趣:
你的例子添加了一些資料,使其更有趣:
data MySum
= CaseA A
| CaseB B
| CaseC C
| CaseD D
-- `partitionSum'對你的型別起作用所需要的一切。
deriving instance GHC.Generic MySum
instance Generic MySum
資料 A = A Int 衍生 Show
資料 B = B String Int 衍生出Show。
資料 C = C 衍生 Show
資料 D = D Integer 衍生 Show
test = partitionSum $
[CaseD $ D 0。CaseB $ B "x" 1, CaseA $ A 2, CaseA $ A 3, CaseB $ B "y" 4, CaseB $ B "z" 5] 。
結果是:
Comp1 {unComp1 = [I (A 2) : * Nil,I (A 3) : * Nil]} 。 * Comp1 {unComp1 = [I (B "x" 1) : * Nil,I (B "y" 4) : * Nil,I (B "z" 5) : * Nil]} :* Comp1 {unComp1 = []} : * Comp1 {unComp1 = [I (D 0) : * Nil] } :*Nil
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/316952.html
標籤:
