繁体   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