依赖类型的类语法

Class syntax on dependent type

提问人:Andrey 提问时间:11/15/2023 最后编辑:Andrey 更新时间:11/15/2023 访问量:76

问:

最近,我尝试为二维向量实现线性映射类。 在我尝试将向量的基本类型(基环)更改为通用类型之前,一切都很好。 我有以下类型的向量:

data Vector a = V a a

(据我所知,我不能或不应该指定是什么。a

我想有一个用于线性映射的类(而不是类型)。 它背后的原因:我想要一个非常具体的线性映射,比如旋转,它依赖于一组不同的参数(甚至可能不依赖于任何参数,例如 x-reflection 没有参数),但它们都有一个目的——应用于向量(这就是为什么我称它为类)。

我尝试定义以下类

class LinearMap m where
  apply :: m -> Vector a -> Vector a

并说我想要两个实例

data Rotation a = Rot a

instance LinearMap (Rotation a) where
  Rot phi `apply` V x y = V (c * x - s * y) (s * x + c * y)
    where
      (c, s) = (cos phi, sin phi)

但它给了我一个错误,因为我期望一个类型

apply :: Rotation a -> Vector a -> Vector a

而编译器预期

apply :: Rotation a -> Vector b -> Vector b

即使类型绑定不是问题,如下所示

data Reflection = XRefl | YRefl

instance LinearMap Reflection where
  XRefl `apply` V x y = V (-x) y
  YRefl `apply` V x y = V x (-y)

我收到一个错误,因为现在和必须是 , 但我不明白我应该在哪里编写实例。xyNum

一个简单的选择是编辑类并添加类似内容的内容LinearMap

class LinearMap m where
  apply :: Num a => m -> Vector a -> Vector a

但这不是我想做的(我希望你看到我的动机), 它也不能解决问题。Rotation

UPD:这是我想出的(不太高兴)

class LinearMap m a | m -> a where
  apply :: m -> Vector a -> Vector a

现在没问题:Rotation

instance Floating a => LinearMap (Rotation a) a where
  -- apply = ...

但需要更新Reflection

data Reflection a = XRefl | YRefl

所以我可以定义一个实例

instance Num a => LinearMap (Reflection a) a where
  -- apply = ...

因为取决于类定义中的样子。 但现在我不高兴这取决于某些东西(这不应该是对的吗?amLinearMapReflection

哈斯克尔

评论

0赞 Fyodor Soikin 11/15/2023
你真的需要功能依赖吗?
0赞 n. m. could be an AI 11/15/2023
向量空间定义在基场之上(或者,如果您愿意,模块定义在基环之上),并且这些空间总是具有加法逆。但是在 Haskell 中,一元减函数被限制为 ,所以如果你使用它,你的基环/字段会自动限制为 。NumNum

答:

1赞 chi 11/15/2023 #1

方法 1:使用类型族和约束

这可能不是最优雅的方法,但......一种方法是在类中添加对 中使用的类型的依赖约束aVector a

这是课程:

{-# LANGUAGE TypeFamilies #-}

import Data.Kind

data Vector a = V a a

class LinearMap m where
  type CMap m (a :: Type) :: Constraint
  apply :: CMap m a => m -> Vector a -> Vector a

这里是一个约束,定义了工作所需的内容。CMap m aapply

data Rotation a = Rot a

instance LinearMap (Rotation a) where
  type CMap (Rotation a) a' = (Floating a, a ~ a')
  Rot phi `apply` V x y = V (c * x - s * y) (s * x + c * y)
    where
      (c, s) = (cos phi, sin phi)

对于 ,我们需要在 中相同,因此我们需要类型相等。我们也要求这样才能使用。Rotation aaaVector aa ~ a'Floatingcos

data Reflection = XRefl | YRefl

instance LinearMap Reflection where
  type CMap Reflection a' = (Num a')
  XRefl `apply` V x y = V (-x) y
  YRefl `apply` V x y = V x (-y)

对于 ,我们只需要 .允许使用任何数字。RotationNuma'

方法 2:只需删除 fundep (?)

我现在想知道在类型类中使用这两个参数是否更好,就像 中一样,并将约束移动到实例中。我们只是避免 fundep .LinearMap m am -> a

class LinearMap m a where
  apply :: m -> Vector a -> Vector a

data Rotation a = Rot a

instance Floating a => LinearMap (Rotation a) a where
  Rot phi `apply` V x y = V (c * x - s * y) (s * x + c * y)
    where
      (c, s) = (cos phi, sin phi)

data Reflection = XRefl | YRefl

instance Num a => LinearMap Reflection a where
  XRefl `apply` V x y = V (-x) y
  YRefl `apply` V x y = V x (-y)

我错过的这种更简单的方法有什么问题吗?我们不会在类型推断过程中进行推断,因为我们缺少 fundep。这是一个真正的问题吗?aRotation a

1赞 leftaroundabout 11/15/2023 #2

据我所知,我不能或不应该指定什么是a

你绝对可以指定是什么。你是否应该是另一回事。大多数 Haskell 社区都使用线性包,这确实使标量类型完全多态化。IMO 这没什么意义,因为在实践中只有极少数类型作为标量有意义,并且向量类型是完全参数化函子是明智的(因为从概念上讲,基的选择是任意的,但映射非线性函数会破坏对称性)。a

因此,我提倡的实际上是使用单态向量类型,或者至少将它们视为单态

data Vector2D a = Vector2D a a

type ℝ² = Vector2D Double

同样,给定的线性映射实例将特定于一种具体的向量类型。有两种表达方式:

  • 具有关联的类型系列。事实上,您可能想要两个,用于域和共同域:

     class LinearMapTF m where
       type LinMapDomain m :: Type
       type LinMapCodomain m :: Type
       apply :: m -> LinMapDomain m -> LinMapCodomain m
    

    这是 IMO 最清楚的,但这意味着每个 linearmap 类型都有一个特定的域和共域,特别是你不能对不同类型的域执行操作。这与函数依赖性遇到的问题相同。Reflection

  • 作为多参数类型类。您在原始版本中已经拥有了它,但是我会从界面中删除标量类型以支持抽象向量空间,并且如果您想允许某些类型的线性映射作用于不同的空间,也会删除函数依赖关系。坚持内同态的具体情况,它只是

     class LinearMapMP m v where
       apply :: m -> v -> v
    

尽管将向量空间视为非参数实体,但在这两种情况下,您都可以完美地交换标量类型。 仍然可以Rotation

data Rotation a = Rot a

instance LinearMapTF (Rotation a) where
  type LinMapDomain (Rotation a) = Vector2D a
  type LinMapCodomain (Rotation a) = Vector2D a
  Rot phi `apply` V x y = ...

instance a~b => LinearMapMP (Rotation a) (Vector2D b) where
  Rot phi `apply` V x y = ...

在此示例中,这两种方法基本等效。在反射的情况下,它们是不同的:

data ReflectionFxd a = XRefl | YRefl

instance LinearMapTF (ReflectionFxd a) where
  type LinMapDomain (ReflectionFxd a) = Vector2D a
  type LinMapCodomain (ReflectionFxd a) = Vector2D a
  apply = ...

data ReflectionArb = XRefl | YRefl

instance LinearMapMP ReflectionArb (Vector2D a) where
  apply = ...

旁注:你所问的都与线性映射无关。你的例子实际上是作用于流形的群的具体情况。例如,在球体上使用旋转是很有意义的。这是不使用标量参数向量类型而使用表示拓扑空间的抽象类型的另一个动机。我还有一个朝这个方向发展的一揽子计划