我有一个这样的功能:
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
型的Float
,Double
,Rational
或Complex
(例如Complex Double
或Complex Rational
)。
但是现在,Complex
除了参数,我想允许alpha
。但是如果a
是类型,Complex b
则alpha
必须是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)
我希望我清楚。我怎样才能做到这一点呢?
每个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开始的实际向量空间。
如果实例被定义为我会做,那就不会工作。实际上已经讨论过了,也许将来会改变。
如果我正确理解,可以为此使用带有关联的类型族的类型类:
{-# 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)
如果类型变量a
为Rational
,则:
alpha
具有类型Rational
(因为BaseFracType Rational
与相同Rational
)inject alpha
也有类型 Rational
inject alpha
是alpha
如果类型变量a
为Complex 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)。