当前位置: 首页 > 知识库问答 >
问题:

我可以一般地计算类型的基数吗?

督劲
2023-03-14

我想有一个类型类,告诉我各种类型有多大。

data Cardinality = Finite Natural | Infinite
class Sized a where cardinality :: Cardinality

编写实例非常简单;例如:

instance Sized Void where cardinality = Finite 0
instance Sized ()   where cardinality = Finite 1
instance Sized Bool where cardinality = Finite 2

instance Sized a => Sized [a] where
    cardinality = case cardinality @a of
        Finite 0 -> Finite 1
        _ -> Infinite

data X = X Y
data Y = Y X X
instance Sized X where cardinality = Finite 1
instance Sized Y where cardinality = Finite 1

事实上,它是如此简单,感觉它应该是自动化的。也许通用编程会有所帮助?

class GSized f where gcardinality :: Cardinality
class Sized a where
    cardinality :: Cardinality
    default cardinality :: (Generic a, GSized (Rep a)) => Cardinality
    cardinality = gcardinality @(Rep a)

大多数实例都非常简单:

instance GSized V1 where gcardinality = Finite 0
instance GSized U1 where gcardinality = Finite 1
instance GSized f => GSized (M1 i c f) where gcardinality = gcardinality @f
instance (GSized f, GSized g) => GSized (f :+: g) where
    gcardinality = case (gcardinality @f, gcardinality @g) of
         (Finite n, Finite n') -> Finite (n+n')
         _ -> Infinite
instance (GSized f, GSized g) => GSized (f :*: g) where
    gcardinality = case (gcardinality @f, gcardinality @g) of
        (Finite 0, _) -> Finite 0
        (_, Finite 0) -> Finite 0
        (Finite n, Finite n') -> Finite (n*n')
        _ -> Infinite

但是后来我被卡住了。对于单个字段来说,简单明了的事情肯定行不通:

instance Sized c => GSized (K1 i c) where
    gcardinality = cardinality @c

对于递归类型,这是一个非常简单的无限循环。我已经尝试了各种方法来丰富这里涉及的两个班级。

  • 我将cardinalitygcardinality概括为函数,这样我就可以假设我已经知道递归出现的大小。然后在K1实例中,我可以问:如果所有递归实例都是无人居住的,那么您会有多大?如果您的所有递归实例都有一个居住者呢?等等
  • 我从一个基数推广到一个“递归关系”,比如x=abxx=abx的预期含义是类型x具有根本不涉及递归的a居民,以及可以与递归调用配对的b值。(我们可以定义x*x=x,而不会丢失任何感兴趣的内容。)然后像x=n0x这样的方程对应于枚举,x=0x对应于平凡的1元素递归类型,x=abx是无限的
  • 我使用了懒惰的自然法则,而不是高效的法则
  • 我跟踪了一组可能的基数,随着新信息的学习,一个接一个地排除它们
  • 我尝试使用Generic1而不是Generic

所有这些最终都不起作用。似乎有三种核心、难题/故障模式

  • 递归类型的K1定义中的一个循环,或者对于递归类型它可以正常工作,但是对于相互递归的类型它会失败
  • K1既用于递归引用,也用于普通字段,而且似乎没有一种简单的方法来区分它们
  • 对于简单的递归类型,选择Finite 0而不是Finite 1

有没有办法解决这些问题?上面的[Void][()]XY的通用实例都已定义且正确吗?

共有1个答案

郭乐湛
2023-03-14
{-# LANGUAGE DeriveGeneric, TypeFamilies, ScopedTypeVariables, UnicodeSyntax
           , TypeApplications, AllowAmbiguousTypes
           , DataKinds, PolyKinds, DefaultSignatures
           , FlexibleInstances, DeriveAnyClass #-}

import GHC.Generics
import Numeric.Natural
import Data.Void
import Data.Proxy
import GHC.TypeLits

data Cardinality = Finite Natural | Infinite
 deriving (Show)

instance Sized Void where cardinalityIC _ = Finite 0
instance Sized ()   where cardinalityIC _ = Finite 1
instance Sized Bool where cardinalityIC _ = Finite 2

instance Sized a => Sized [a] where
    cardinalityIC rctxt = case cardinalityIC @a rctxt of
        Finite 0 -> Finite 1
        _ -> Infinite

data TypeIdentifier = TypeIdentifier
  { typeName, moduleName, packageName :: String }
  deriving (Eq, Show)

class GSized f where gcardinalityIC :: [TypeIdentifier] -> Cardinality
class Sized a where
    cardinalityIC :: [TypeIdentifier] -> Cardinality
    default cardinalityIC :: (Generic a, GSized (Rep a))
              => [TypeIdentifier] -> Cardinality
    cardinalityIC = gcardinalityIC @(Rep a)

cardinality :: ∀ a . Sized a => Cardinality
cardinality = cardinalityIC @a []

instance GSized V1 where gcardinalityIC _ = Finite 0
instance GSized U1 where gcardinalityIC _ = Finite 1

instance GSized f => GSized (M1 C c f) where gcardinalityIC = gcardinalityIC @f
instance GSized f => GSized (M1 S c f) where gcardinalityIC = gcardinalityIC @f

instance (GSized f, KnownSymbol tn, KnownSymbol mn, KnownSymbol pn)
            => GSized (D1 ('MetaData tn mn pn nt) f) where
  gcardinalityIC rctxt
    | thisType`elem`rctxt  = Infinite
    | otherwise            = gcardinalityIC @f $ thisType : rctxt
   where thisType = TypeIdentifier
                     (symbolVal $ Proxy @tn)
                     (symbolVal $ Proxy @mn)
                     (symbolVal $ Proxy @pn)
         moduleName = symbolVal $ Proxy @tn

instance (GSized f, GSized g) => GSized (f :+: g) where
    gcardinalityIC rctxt = case (gcardinalityIC @f rctxt, gcardinalityIC @g rctxt) of
         (Finite n, Finite n') -> Finite (n+n')
         _ -> Infinite
instance (GSized f, GSized g) => GSized (f :*: g) where
    gcardinalityIC rctxt = case (gcardinalityIC @f rctxt, gcardinalityIC @g rctxt) of
        (Finite 0, _) -> Finite 0
        (_, Finite 0) -> Finite 0
        (Finite n, Finite n') -> Finite (n*n')
        _ -> Infinite


instance Sized c => GSized (K1 i c) where
    gcardinalityIC = cardinalityIC @c

data Foo = F0 Bool | F1 Bool
 deriving (Generic, Sized)

data Bar = B0 Bool | B1 Bar
 deriving (Generic, Sized)

data Never = Never Never
 deriving (Generic, Sized)
ghci> cardinality @Foo
Finite 4
ghci> cardinality @Bar
Infinite
ghci> cardinality @Never
Infinite

正如李耀霞所说,最后一个没有意义,因为nevernever没有NF non-⊥ 价值观不确定是否有一个好的方法来考虑这一点。

 类似资料:
  • 问题内容: 是否可以在Java中以反射方式实例化泛型?使用此处描述的技术,由于类标记不能通用,我会遇到错误。请看下面的例子。我想实例化一些实现Creator的Creator子类。实际的类名作为命令行参数传递。这个想法是为了能够在运行时指定Creator的实现。还有另一种方法可以完成我在这里要做的事情吗? 编辑:我喜欢Marcus的方法,因为它是最简单,最实用的方法,不会绕开整个泛型的东西。我可以在

  • 我想为我的Hazelcast集群计算一个最佳的分区数,但是,我找不到一个参数来作为计算的基础。271的默认分区可能足够,也可能不够,我不确定。 为了简单起见,如果我假设我的集群将有大约5000万个条目在50个节点上拆分,那么理想的分区数量是多少以及如何派生到这个数字? 谢谢你,迪利什

  • 在这一点上,如果你有兴趣像MPL一样进行类型计算,你可能会想知道Hana如何帮助你。不用担心,Hana提供了一种通过将类型表示为值来执行具有大量表达性的类型计算的方法,就像我们将编译时数字表示为值一样。 这是一种全新的接触元编程的方法,如果你想熟练使用Hana,你应该尝试将你的旧MPL习惯放在一边。 但是,请注意,现代C++的功能,如自动推导返回类型,在许多情况下不需要类型计算。 因此,在考虑做一

  • 那也许我可以 还是这是不可能的?也许只有在编译时了解了所有的信息,您才能做这种事情?

  • 问题内容: 我可以在python中计算exp(1 + 2j)吗? 问题答案: 您需要此功能的复杂版本: 参见http://docs.python.org/library/cmath.html

  • 我试图编写一个程序,计算文本中字母、单词和句子的数量。我可以假设一个字母是从a到z的任何小写字符或从a到z的任何大写字符,任何由空格分隔的字符序列都应算作一个单词,任何句点、感叹号或问号的出现都表示一个句子的结束。 到目前为止,我可以正确地计算字母和句子的数量,但是我错过了单词的数量: 例如是! 输出应为:3个字母1个单词1个句子 我得到的是:3个字母0个单词1个句子 更新:在printf函数前面