防止参数为复数

Sté*_*ent 10 haskell

我有一个这样的功能:

hypergeom ::
     forall a. (Eq a, Fractional a)
  => Int  -- truncation weight
  -> a    -- alpha parameter (usually 2)
  -> [a]  -- "upper" parameters
  -> [a]  -- "lower" parameters
  -> [a]  -- variables (the eigen values)
  -> IO a
hypergeom m alpha a b x = do
  ......
Run Code Online (Sandbox Code Playgroud)

我选用的约束Fractional a,因为我想可能采取a型的FloatDoubleRationalComplex(例如Complex DoubleComplex Rational)。

但是现在,Complex除了参数,我想允许alpha。但是如果a是类型,Complex balpha必须是type b。例如:

hypergeom ::
  => Int               -- truncation weight
  -> Double            -- alpha parameter (usually 2)
  -> [Complex Double]  -- "upper" parameters
  -> [Complex Double]  -- "lower" parameters
  -> [Complex Double]  -- variables (the eigen values)
  -> IO (Complex Double)
Run Code Online (Sandbox Code Playgroud)

我希望我清楚。我怎样才能做到这一点呢?

lef*_*out 8

每个Haskeller都应该知道vector-space,这是可以使用它的一个应用程序。

hypergeom ::
     ? a. (VectorSpace a, Eq a, RealFrac (Scalar a))
  => Int       -- truncation weight
  -> Scalar a  -- alpha parameter (usually 2)
  -> [a]       -- "upper" parameters
  -> [a]       -- "lower" parameters
  -> [a]       -- variables (the eigen values)
  -> IO a
hypergeom m ? a b x = do
  ......
Run Code Online (Sandbox Code Playgroud)

复杂的情况下

instance (RealFloat v, VectorSpace v) => VectorSpace (Complex v) where
  type Scalar (Complex v) = Scalar v
  s*^(u :+ v) = s*^u :+ s*^v
Run Code Online (Sandbox Code Playgroud)

但是,请注意:我个人不喜欢该特定实例。由于复数是除法代数,因此将它们视为标量类型通常非常有用,即

instance RealFloat a => VectorSpace (Complex a) where
  type Scalar (Complex a) = Complex a
  (*^) = (*)
Run Code Online (Sandbox Code Playgroud)

这是可取的,原因是复数(例如元组)上的自由向量空间实际上将是复数向量空间,而不是从库版本0.16开始的实际向量空间。

如果实例定义为我会做,那就不会工作。实际上已经讨论过了,也许将来会改变。


Dav*_*vid 7

如果我正确理解,可以为此使用带有关联的类型族的类型类:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DefaultSignatures #-}

import           Data.Complex
import           Data.Ratio

class BaseFrac a where
  type family BaseFracType a
  type BaseFracType a = a -- Default type family instance (unless overridden)

  inject :: BaseFracType a -> a

  default inject :: BaseFracType a ~ a => BaseFracType a -> a
  inject = id

instance Integral a => BaseFrac (Ratio a)
instance BaseFrac Float
instance BaseFrac Double

-- etc...

instance Num a => BaseFrac (Complex a) where
  type BaseFracType (Complex a) = a

  inject x = x :+ 0


hypergeom ::
     forall a. (Eq a, Fractional a, BaseFrac a)
  => Int  -- truncation weight
  -> BaseFracType a    -- alpha parameter (usually 2)
  -> [a]  -- "upper" parameters
  -> [a]  -- "lower" parameters
  -> [a]  -- variables (the eigen values)
  -> IO a
hypergeom m alpha a b x = ...
Run Code Online (Sandbox Code Playgroud)

您可能需要向类型类中添加其他方法,但是我认为inject应该提供一些重要的实用程序。

说明

通过写这个解释,我意识到我可能在没有给出我应该给出的背景信息的情况下将几个想法压缩到一个很小的区域。希望这会有所帮助,如果您有任何疑问或感到困惑,请让我知道!

这里有两个主要的相互影响的想法。首先是类型类。我将假定一些有关类型类的基础知识(有关该基础知识有很多资源。如果您愿意,我可以在此处找到一些链接)。

另一个是类型家庭的想法。类型族本质上是一种从类型到类型的函数。有时它们在类型类内部(如此处所示),但不一定必须如此。此外,有时它们是“开放的”,有时它们是“封闭的”(如果它们在类型类内部,则它们实际上是开放的)

封闭式家庭

我认为先看一个不在类型类中的封闭类型家族是有启发性的。考虑一下:

type family Example :: * -> * where
  Example Int = Bool
  Example a   = a
Run Code Online (Sandbox Code Playgroud)

这非常类似于常规的Haskell函数定义,只是它碰巧对类型而不是值进行操作。如果输入是类型Int,则返回该类型Bool。否则,它返回与作为参数的类型相同的类型。

我们可以使用:kind!GHCi中的命令来看到这一点:

? > :kind! Example Int
Example Int :: *
= Bool
? >
? > :kind! Example Char
Example Char :: *
= Char
Run Code Online (Sandbox Code Playgroud)

您还可以将类型同义词视为类型族的一种非常受限制的形式。

该类型族称为“封闭”类,因为您不能在其定义中添加更多“等式”(就像“常规” Haskell函数一样)。

开放式家庭

但是,您也可以具有“开放”类型族,以后可以在其中添加其他方程式。例如:

type family OpenExample :: * -> *


type instance OpenExample [a] = a
type instance OpenExample Text = Char
type instance OpenExample IntSet = Int
-- ^ These just give you the "element type" inside some containers
Run Code Online (Sandbox Code Playgroud)

以后我们可以使用添加新的等式type instance(例如,如果添加新的容器类型,则在此处)。

与类型类关联的类型族

这将我们带到这里的类型族:具有关联的类型族的类型类。这很像开放类型系列,但是输入受类型类约束。同样,每个方程式都在类型类的实例内部。

我提供了一个默认类型实例(的第二行class BaseFrac),如果没有提供,它将自动使用。要Double显式写出实例(不使用默认值),它看起来像:

instance BaseFrac Double where
  type BaseFracType Double = Double
Run Code Online (Sandbox Code Playgroud)

请注意,这与type instance语法有多相似。

我还提供了该inject方法的默认实现。该默认可以被使用,如果BaseFracType a相同a(这是约束BaseFracType a ~ a默认签名方式)。

该约束确实适用于instance使用默认BaseFracType定义的任何对象(因为它只是type BaseFracType a = a),这就是为什么这些“空”实例定义会自动起作用的原因。

因此,对于目前给出的情况下,BaseFracType Double是一样的Double(从使用的(默认)家庭类型定义Double的实例BaseFrac类),并BaseFracType (Complex a)是一样的a(从给出的类型族实例定义 Complex a的实例BaseFrac类) 。

有什么inject

这种解释说明了为什么会计算出类型,但是接下来的问题是我们如何实际使用它以及为什么inject重要?幸运的是,这两个问题的答案是关联的。

inject本质上为您提供了一种将“基本”(“一维”)分数值放入具有BaseFrac该类实例的任何类型中的方法。

对于大多数类型,这只是标识函数(因为Double已经是“基本”分数值等)。对于Complex a,这是不同的。它只是构造一个虚数为零且其自变量为实数的复数。在这种情况下,它是type的函数inject :: Num a => a -> Complex a

这是一个inject基于您提供的功能的简单示例,具有完整的通用性(此功能适用于任何BaseFrac输入):

hypergeom :: forall a. (Eq a, Fractional a, BaseFrac a)
  => Int
  -> BaseFracType a
  -> [a]
  -> [a]
  -> [a]
  -> IO a
hypergeom m alpha a b x = return (inject alpha * head a)
Run Code Online (Sandbox Code Playgroud)

如果类型变量aRational,则:

  • alpha具有类型Rational(因为BaseFracType Rational与相同Rational
  • inject alpha 也有类型 Rational
  • 的价值inject alphaalpha

如果类型变量aComplex Double,则:

  • alpha具有类型Double(因为BaseFracType (Complex Double)与相同Double
  • inject alpha 有类型 Complex Double
  • 的值inject alpha就是alpha :+ 0

您还可以在:kind!此处使用GHCi 命令:

? > :kind! BaseFracType (Complex Double)
BaseFracType (Complex Double) :: *
= Double
Run Code Online (Sandbox Code Playgroud)

如果有什么令人困惑的地方,您可以告诉我,我应该能够澄清。

附加材料

本项目的房型家庭一些更多的信息在这里。可能最相关的部分是有关类型同义词实例的部分(这些是我们讨论过的与类型类没有关联的类型族),关于封闭类型族的子节和有关类型族的子节

请注意,该页面还讨论了数据系列,在这里它们并不是特别相关(数据系列有点像“开放式” GADT)。

  • @nm我以为实例重叠会遇到问题,但我没有尝试过(尽管我知道有时实例在一定程度上可以重叠)。如果没有这些问题,那肯定很方便! (2认同)
  • @nm看起来确实确实会遇到重叠实例错误的问题。每当您有一个看起来像“ instance forall a”的实例时。... => SomeClass a`,那么您很有可能遇到重叠的实例问题(因为GHC在搜索匹配的类型类“实例”时会忽略该约束)。 (2认同)