我正在努力掌握种类,类型和术语(或值,不确定哪个是正确的)以及用于操作它们的GHC扩展。我知道我们可以使用TypeFamilies来编写带有类型的函数,现在我们也可以使用DataKinds,PolyKinds等在某种程度上操作Kinds。我已经在Singleton类型上阅读了这个paper这看起来很有趣,虽然我还没有完全理解还没完成。这一切让我想知道,有没有办法创建一个函数,根据Term或Value级别的计算来计算返回类型?这是依赖类型实现的吗?
这是我正在思考的一个例子
data Type1
data Type2
f :: Type1 -> Type2 -> (Type1 or Type2)--not using Either or some "wrapper" ADT
- 更新--------
基于大量的研究和帮助,我现在已经清楚,无论我启用多少扩展,函数的返回类型永远不能基于Haskell中的值级别的表达式来计算。所以我发布了更多我的实际代码,希望有人能帮我决定前进的最佳方式。我正在写一个小型库,其中以圆锥曲线和二次曲面作为基本类型。对这些类型的操作涉及计算它们之间的交叉点。两个曲面的交点是圆锥曲线的类型之一,包括像点一样的退化(除了圆锥曲线之外,实际上需要另一种类型的曲线,但除了该点之外)。精确的曲线返回类型只能通过运行时交叉曲面的值来确定。圆柱 - 平面交点可以产生无,线,圆或椭圆。 我的第一个计划是使用ADT这样构建曲线和曲面......
data Curve = Point !Vec3
| Line !Vec3 !UVec3
| Circle !Vec3 !UVec3 !Double
| Ellipse !Vec3 !UVec3 !UVec3 !Double !Double
| Parabola !Vec3 !UVec3 !UVec3 !Double
| Hyperbola !Vec3 !UVec3 !UVec3 !Double !Double
deriving(Show,Eq)
data Surface = Plane !Vec3 !UVec3
| Sphere !Vec3 !Double
| Cylinder !Vec3 !UVec3 !Double
| Cone !Vec3 !UVec3 !Double
deriving(Show,Eq)
...这是最直接的,并且有一个很好的封闭代数类型的优势,我喜欢。在这种表示中,交点的返回类型很简单,它只是曲线。这种表示的缺点是这些类型的每个函数都必须为每种类型进行模式匹配,并处理所有对我来说都很麻烦的排列。 Surface-Surface交叉函数将有16种模式匹配。
下一个选项是保持每个曲面和曲线类型。像这样,
data Point = Point !Vec3 deriving(Show,Eq)
data Line = Line !Vec3 !UVec3 deriving(Show,Eq)
data Circle = Circle !Vec3 !UVec3 !Double deriving(Show,Eq)
data Ellipse = Ellipse !Vec3 !UVec3 !UVec3 !Double !Double deriving(Show,Eq)
data Parabola = Parabola !Vec3 !UVec3 !UVec3 !Double deriving(Show,Eq)
data Hyperbola = Hyperbola !Vec3 !UVec3 !UVec3 !Double !Double deriving(Show,Eq)
data Plane = Plane !Vec3 !UVec3 deriving(Show,Eq)
data Sphere = Sphere !Vec3 !Double deriving(Show,Eq)
data Cylinder = Cylinder !Vec3 !UVec3 !Double deriving(Show,Eq)
data Cone = Cone !Vec3 !UVec3 !Double deriving(Show,Eq)
从长远来看,它看起来可能更灵活,并且很好且粒度很好但需要包装器ADT才能处理来自交集函数的多个返回类型或构建一般“曲线”列表或“表面”因为它们之间没有关系。我可以使用Type Classes和existentials对它们进行分组,但后来我丢失了原始类型,我不喜欢它。
这些设计的妥协使我尝试了这个......
---------------------------------------------------------------
-- Curve Types
---------------------------------------------------------------
type Pnte = Curve PNT
type Line = Curve LIN
type Circ = Curve CIR
type Elli = Curve ELL
type Para = Curve PAR
type Hype = Curve HYP
-----------------------------------------------
data CrvIdx = PNT
| LIN
| CIR
| ELL
| PAR
| HYP
-----------------------------------------------
data Curve :: CrvIdx → * where
Pnte :: !Vec3 → Curve PNT
Line :: !Vec3 → !UVec3 → Curve LIN
Circ :: !Vec3 → !UVec3 → !Double → Curve CIR
Elli :: !Vec3 → !UVec3 → !UVec3 → !Double → !Double → Curve ELL
Para :: !Vec3 → !UVec3 → !UVec3 → !Double → Curve PAR
Hype :: !Vec3 → !UVec3 → !UVec3 → !Double → !Double → Curve HYP
---------------------------------------------------------------
-- Surface Types
---------------------------------------------------------------
type Plne = Surface PLN
type Sphe = Surface SPH
type Cyln = Surface CYL
type Cone = Surface CNE
-----------------------------------------------
data SrfIdx = PLN
| SPH
| CYL
| CNE
-----------------------------------------------
data Surface :: SrfIdx → * where
Plne :: !Vec3 → !UVec3 → Surface PLN
Sphe :: !Vec3 → !Double → Surface SPH
Cyln :: !Vec3 → !UVec3 → !Double → Surface CYL
Cone :: !Vec3 → !UVec3 → !Double → Surface CNE
...起初我以为它会给我一些神奇的,最好的两个世界场景,我可以通过“曲线”(如列表或交叉点返回类型)引用任何曲线类型,并且还具有可用的完整类型(Curve CrvIdx)使用多参数类型类等以粒度样式编写函数。我很快发现这不像我希望的那样好用question。我顽固地继续用力撞墙,试图找到一种方法来编写一个函数,可以根据运行时参数的几何属性从我的GADT中选择一个返回类型,现在意识到这不会发生。那么现在问题是什么是一种有效而灵活的方式来表示这些类型以及它们之间的相互作用?谢谢。
答案 0 :(得分:2)
简答:不。您需要使用包装器ADT,Data.Dynamic
或type-family/associated
type。
类型系列可能是你想要的最接近的东西,但同样, 类型需要能够在编译时决定。例如:
{-# LANGUAGE TypeFamilies #-}
data Red
data Green
data Blue
data Yellow
data Cyan
data Violet
type family MixedColor a b
type instance MixedColor Red Red = Red
type instance MixedColor Red Green = Yellow
type instance MixedColor Red Blue = Violet
type instance MixedColor Green Red = Yellow
type instance MixedColor Green Green = Green
type instance MixedColor Green Blue = Cyan
-- etc ..
mixColors :: c1 -> c2 -> MixedColor c1 c2
mixColors = undefined
这里,mixColors
函数基本上可以返回任何类型的值,
但返回类型必须是类型系列MixedColor
的实例
这样编译器就可以根据参数类型推断出实际的返回类型。
您可以使用类型族和类型类来构建相对复杂的类 类型函数,让您越来越接近依赖的功能 类型,但这意味着您的数据需要使用足够的类型级别进行修饰 用于进行所需类型计算的信息。
如果您需要编码,最近推出的type-level natural numbers会非常有用 您的类型中的数值计算。
编辑:另外,我不确定你为什么不愿意使用ADT(也许你需要更详细地描述你的用例?),因为编码例如函数可以返回Type1
或Type2
的事实完全 ADT编码的信息类型非常自然并且是惯用的。