haskell 依赖类型的类语法

hfwmuf9z  于 2023-11-18  发布在  其他
关注(0)|答案(2)|浏览(145)

最近,我尝试为2d vectors实现linear maps类。它很好,直到我尝试将vectors的基本类型(base-ring)更改为通用类型。我有以下类型的vectors:

data Vector a = V a a

字符串
(As据我所知,我不能或不应该指定a是什么。
我想有一个线性Map的类(而不是类型),其背后的原因是:我想有一个非常特定的线性Map,比如旋转,它依赖于不同的参数集(甚至可能不依赖于任何参数,例如x反射没有参数),但它们都有一个目的-应用于向量(这就是为什么我称之为类)。
我尝试定义以下类

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)


我得到一个错误,因为现在xy必须是Num的示例,但我不知道应该在哪里写示例。
一个简单的选择是编辑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 = ...


因为a * 依赖于m,就像在LinearMap类定义中一样。但是现在我不高兴Reflection依赖于某些东西(它不应该对吗?)。

9q78igpj

9q78igpj1#

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

这可能不是最优雅的方法,但是.
这是类:

{-# 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 a是一个约束,定义了apply工作所需的条件。

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 a,我们需要aVector a中的a相同,因此我们需要类型相等a ~ a'。我们还需要Floating,以便可以使用cos

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)


对于Rotation,我们只需要Num。任何数字a'都是允许的。

方法二:去掉fundep(?)

我现在想知道是否在类型类中使用两个参数会更好,就像在LinearMap m a中一样,并将约束移动到示例中。我们只是避免了fundep m -> 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)


我错过的这个简单的方法有什么问题吗?我们在类型推断过程中没有从x1m17 n1x推断出x1m16 n1x,因为我们缺乏fundep。这是一个真实的问题吗?

ukqbszuj

ukqbszuj2#

据我所知,我不能或不应该指定a是什么
你绝对可以指定a是什么。你是否应该是另一回事。大多数Haskell社区使用linear包,这确实使标量类型完全多态。IMO这没有什么意义,因为只有很少的类型在实践中作为标量有意义,而向量类型是完全参数化的函子是不明智的(因为在概念上,基的选择是任意的,但是fMap非线性函数破坏了对称性)。
所以what I advocate for实际上是使用 monomorphic vector类型,或者至少将它们视为monomorphic:

data Vector2D a = Vector2D a a

type ℝ² = Vector2D Double

字符串
同样,一个给定的线性Map示例将特定于一个具体的向量类型。有两种表达方式:

  • 有一个关联的类型家族。事实上,你可能需要两个,分别用于domain和codomain:
class LinearMapTF m where
   type LinMapDomain m :: Type
   type LinMapCodomain m :: Type
   apply :: m -> LinMapDomain m -> LinMapCodomain m


这是我最清楚的,但它意味着每个线性Map类型都有一个特定的域和共域,特别是你不能让Reflection作用于不同类型的域。这和你的函数依赖是一样的。

  • 作为一个多参数类型类。你已经在原来的,但我会删除标量类型的接口,有利于抽象向量空间,并删除功能依赖,如果你想让一些类型的线性Map,以不同的空间。坚持自同态的具体情况下,它只是
class LinearMapMP m v where
   apply :: m -> v -> v


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

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 = ...


旁注:你所问的都与线性Map无关。你的例子实际上是 * 群作用于流形 * 的具体情况。例如,在球面上使用旋转是很有意义的。这是另一个动机,不要使用标量参数向量类型,而是表示拓扑空间的抽象类型。我也是have a package going in that direction

相关问题