簡體   English   中英

如何在 Haskell 中定義類型依賴函數族

[英]How to define a family of type dependent function in Haskell

這是一個(系列)Haskell 問題。 我對 Haskell 相當陌生。

假設我們有一個 4 元組 (a1,a2,a3,a4)。 我們如何定義一個函數kth ,它給出了這個元組中的第 k 個元素? 例子,

kth (1,"A",'b',True) 3 = 'b'

如果a1、a2、a3、a4的類型相同,那么它的定義就比較簡單了。 例如,如果它們都是整數:

kth :: (Int,Int,Int,Int) -> Int -> Int
kth (a1,a2,a3,a4) 1 = a1
kth (a1,a2,a3,a4) 2 = a2
kth (a1,a2,a3,a4) 3 = a3
kth (a1,a2,a3,a4) 4 = a4

我懷疑為什么這不簡單是因為 Haskell 必須事先知道類型。 在庫函數fstsnd ,Haskell 知道輸出類型是形式的第一個元素的類型,輸出類型是后者的第二個元素的類型。 因此,沒有歧義。 kth ,輸出類型取決於第二個輸入,因此 Haskell 無法根據語法進行類型檢查。

現在,假設我們有第 n 個元組 (a1,a2,...,an)。 我們可以定義一個長度函數族,使得

lengthTuple :: a -> Int
lengthTuple (a1,a2,...,an) = n

這種問題(依賴類型)在 Haskell 中仍然是一個令人頭疼的問題。 Prelude 中的元組不太適合這種任務(雖然也許可行)。 但是您可以使用具有依賴類型的大小向量來解決此類問題。

示例: https : //www.schoolofhaskell.com/user/konn/prove-your-haskell-for-great-safety/dependent-types-in-haskell

如果索引必須是Int ,則無法實現您的函數,但如果它是自定義的“單例”索引類型,則可以實現。 本質上,如果我們想模仿依賴類型,最好的選擇是大量傳遞單例,將類型級別的值連接到術語級別的值。

這是一個例子:

{-# LANGUAGE GADTs, DataKinds, TypeFamilies #-}
{-# OPTIONS -Wall #-}

-- custom index type
data Ix = I1 | I2 | I3 | I4

-- associated singleton type (this could be autogenerated using the singletons library)
data SIx (ix :: Ix) where
  SI1 :: SIx 'I1
  SI2 :: SIx 'I2
  SI3 :: SIx 'I3
  SI4 :: SIx 'I4

-- type level function
type family T (ix :: Ix) a1 a2 a3 a4 
type instance T 'I1 a1 _ _ _ = a1
type instance T 'I2 _ a2 _ _ = a2
type instance T 'I3 _ _ a3 _ = a3
type instance T 'I4 _ _ _ a4 = a4

-- our "dependent" tuple selector
choose :: (a1, a2, a3, a4) -> SIx ix -> T ix a1 a2 a3 a4
choose (x1, _, _, _) SI1 = x1
choose (_, x2, _, _) SI2 = x2
choose (_, _, x3, _) SI3 = x3
choose (_, _, _, x4) SI4 = x4

如果需要,我們可以使用存在性包裝器(作為一種依賴和類型)“隱藏” SIx ixT ix a1 a2 a3 a4ix參數,構建一個函數,給定“某個索引”返回“某個組件”。

如果我們有真正的依賴類型,這會方便得多。 盡管如此,這是我們目前為在運行時進行類型擦除所付出的代價。 如果 Haskell 有一天添加了未擦除的pi a . ... pi a . ...鍵入已擦除的forall a . ... forall a . ...我們現在有了,我們將擁有更多的控制權。

正如一些評論中所建議的那樣,簡短的回答是你不應該認真地在 Haskell 中這樣做。 如果您發現自己需要編寫可以對不同大小的元組進行操作的函數,那么您對 ​​Haskell 的編程就錯了。

然而,定義像lengthTuple這樣的函數的慣用方法是使用具有顯式實例的類型類,用於不同的元組大小。 如果這是用於庫,則選擇一些上限並將實例寫入該大小。 一個合理的選擇可能是 15 元組,因為這也是具有Show實例的最大元組:

> (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
> (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
<interactive>:72:1: error:
    • No instance for (Show ...

因此, lengthTuple的定義如下所示:

class Tuple a where lengthTuple :: a -> Int
instance Tuple (a,b) where lengthTuple _ = 2
instance Tuple (a,b,c) where lengthTuple _ = 3
instance Tuple (a,b,c,d) where lengthTuple _ = 4
...up to 15...

乏味,但很標准。

值得注意的是,可以使用Data.Data泛型在沒有任何樣板的情況下編寫lengthTuple 這些泛型提供了一種以相當通用的方式折疊代數數據類型的結構的方法,您可以使用Const函子在計算字段數時忽略數據類型的實際內容:

import Data.Data
import Data.Functor.Const
lengthTuple :: (Data a) => a -> Int
lengthTuple = getConst . gfoldl (\(Const n) _ -> Const (n+1))
                                (\_ -> Const 0)

這很好用,盡管沒有直接的方法將其限制為元組,並且您可能會發現它對非元組的返回值有些令人驚訝:

> lengthTuple (1,2,3,4)
4
> lengthTuple ""
0
> lengthTuple "what the heck?"
2

kth要困難得多。 你的直覺是對的。 因為表達式kth tuple n的類型取決於而不是參數n的類型,所以簡單的定義是不可能的。 其他答案涵蓋了幾種方法。

暫無
暫無

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

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