簡體   English   中英

在Haskell中將具有已知長度的列表轉換為嵌套對的最簡單方法是什么?

[英]What is the easiest way to turn a list with known length into nested pairs in Haskell?

如何將具有已知長度的列表轉換為嵌套對? 換句話說,填充下面的類型孔最方便的方法是什么?

_ [1,2]       :: (Int,Int)
_ [1,2,3]     :: ((Int,Int),Int)
_ [1,2,3,4]   :: (((Int,Int),Int),Int)
_ [1,2,3,4,5] :: ((((Int,Int),Int),Int),Int)

編輯:請注意,類型孔不必是相同的功能,我正在尋找一個方便的模式(如果存在方便的模式)來填補空洞。

也許是這樣的:

step f xs = (f (init xs), last xs)
len1 = head
len2 = step len1
len3 = step len2
len4 = step len3

在ghci:

*Main> len4 [1..4]
(((1,2),3),4)

當然也可以通過模式匹配直接實現這些功能之一:

len4' [a,b,c,d] = (((a,b),c),d)

這也不會像有元素一樣遍歷列表,這很好。

與依賴類型的版本一起使用。 首先,讓我們完成樣板:

{-# LANGUAGE
  TemplateHaskell, DataKinds, ScopedTypeVariables,
  FlexibleInstances, PolyKinds, TypeOperators,
  TypeFamilies, GADTs, UndecidableInstances #-}

import Data.Singletons.TH
import qualified GHC.TypeLits as Lit

$(singletons [d| data Nat = Z | S Nat deriving (Eq, Show) |])

這里使用TH純粹是為了減少樣板,我們不會在實際代碼中使用TH。 實際上,上面的內容可以(並且應該)在某個地方的一個包中被考慮(在寫這個答案時,沒有這樣一個包含最新singletons依賴的包)。

tuplify成為一個函數,其返回類型取決於Nat參數。

type family NTup n a where
  NTup (S (S Z))     a = (a, a)
  NTup (S (S (S n))) a = (NTup (S (S n)) a, a)

tuplify :: Sing n -> [a] -> NTup n a
tuplify n as = go n (reverse as) where
  go :: Sing n -> [a] -> NTup n a
  go (SS (SS SZ))     [a, b] = (b, a)
  go (SS (SS (SS n))) (a:as) = (go (SS (SS n)) as, a)
  go _                _      = error "tuplify: length mismatch"

嘗試一下:

tuplify (SS (SS (SS SZ))) [1, 2, 3] -- ((1, 2), 3)

現在寫出自然是非常艱巨的,所以讓我們介紹一些語法糖:

type family N n where
  N 0 = Z
  N n = S (N (n Lit.- 1))

type SN n = Sing (N n)

現在:

tuplify (sing:: SN 10) [1..10] -- (((((((((1,2),3),4),5),6),7),8),9),10)

作為旁注,如果我們將空列表轉換為() (從而也允許單元素嵌套元組),我們的定義變得更加自然:

type family NTup n a where
  NTup Z     a = ()
  NTup (S n) a = (NTup n a, a)

tuplify :: Sing n -> [a] -> NTup n a
tuplify n = go n . reverse where
  go :: Sing n -> [a] -> NTup n a
  go SZ     []     = ()
  go (SS n) (a:as) = (go n as, a)
  go _      _      = error "tuplify: length mismatch"

tuplify (sing:: SN 5) [1..5] -- ((((((),1),2),3),4),5)

這對於依舊類型的Agda來說是一個很好的練習。 在Haskell中,你可以獲得一些接近的東西(也受到Daniel Wagner解決方案的啟發)

class C a b where
   listToTuple :: [a] -> b

instance C a a where
   listToTuple [x] = x

instance C a b => C a (b,a) where
   listToTuple xs = (listToTuple (init xs), last xs)

一些測試:

> listToTuple [1..3::Int] :: ((Int,Int),Int)
((1,2),3)
> listToTuple [0..3::Int] :: (((Int,Int),Int),Int)
(((0,1),2),3)

請注意,返回類型注釋是必需的,否則Haskell無法推斷返回元組必須具有的元素數量。 如果元組和列表長度不匹配,則會發生運行時錯誤。 這幾乎是不可避免的,因為列表不會在其類型中攜帶它們的長度,因此編譯器不能更早地檢查它(與使用向量GADT不同)。

為了擁有這樣的泛型和類型安全函數,您需要依賴類型,以便結果中嵌套元組的數量可能取決於輸入列表的長度。

然而,有可能接近多態遞歸

讓我們定義一個數據類型如下:

data TupleList' r a = Value r | Tuple (TupleList' (r, a) a)
  deriving (Show, Read, Eq, Ord)

type TupleList = TupleList' ()

因此,類型TupleList a的值與()((), a)(((), a), a) TupleList a構,這取決於有多少Tuple構造函數包裝最終的Value

現在我們可以將列表轉換為如下的元組:

fromList :: [a] -> TupleList a
fromList = loop ()
  where
    loop :: r -> [a] -> TupleList' r a
    loop r [] = Value r
    loop r (x:xs) = Tuple (loop (r, x) xs)

請注意, loop使用多態遞歸(作為對TupleList'進行操作的任何函數TupleList' - 其遞歸調用具有簽名(r, a) -> [a] -> TupleList' (r, a) a

示例: mapM_ (print . fromList) (inits [1..4])產生

Value ()
Tuple (Value ((),1))
Tuple (Tuple (Value (((),1),2)))
Tuple (Tuple (Tuple (Value ((((),1),2),3))))
Tuple (Tuple (Tuple (Tuple (Value (((((),1),2),3),4)))))

最簡單的方法是

z   (x:xs) = x
s r (x:xs) = (x, r xs)
toTuples n xs = n xs

toTuples以相反的順序返回對:

 toTuples (s (s (s z))) [1..] == (1,(2,(3,4)))

我們可以使用CPS來解決這個問題:

z   f  xs    = f ()
s r f (x:xs) = r (\p -> (f p, x)) xs
toTuples n (x:xs) = n (const x) xs

然后

toTuples (s (s (s z))) [1..] == (((1,2),3),4)

我們可以定義一些句法糖(我主要是從AndrásKovács的回答中竊取):

{-# LANGUAGE TemplateHaskell, UndecidableInstances, DataKinds, GADTs, TypeFamilies, TypeOperators #-}

import Data.Singletons.TH
import GHC.TypeLits

$(singletons [d| data Nat = Z | S Nat deriving (Eq, Show) |])

z   f  xs    = f ()
s r f (x:xs) = r (\p -> (f p, x)) xs

toTuples n (x:xs) = n (const x) xs

type family Result n r a where
  Result  Z    r a = r
  Result (S n) r a = Result n (r, a) a

run :: Sing n -> (() -> r) -> [a] -> Result n r a
run  SZ     = z
run (SS sn) = s (run sn)

toTuplesN :: Sing n -> [a] -> Result n a a
toTuplesN sn (x:xs) = run sn (const x) xs

type family N n where
  N 0 = Z
  N n = S (N (n - 1))

type SN n = Sing (N (n - 1))

main = print $ toTuplesN (sing :: SN 6) [1..] -- (((((1,2),3),4),5),6)

請注意,代碼也適用於無限列表,因為沒有反轉。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM