Return Type as a result of Term or Value calculation

那年仲夏 提交于 2019-12-11 01:37:25

问题


I'm trying to get a good grasp on Kinds, Types & Terms(or Values, not sure which is correct) and the GHC extensions for manipulating them. I understand that we can use TypeFamilies to write functions with Types and now we can also manipulate Kinds to some extent using DataKinds, PolyKinds etc. I have read this paper on Singleton Types which seems interesting although I don't fully understand it yet. This has all led me to wonder, is there a way to create a function that calculates the return Type based on calculation at the Term or Value level? Is this what Dependent Types achieve?

Here's an example of what I'm thinking

data Type1
data Type2

f :: Type1 -> Type2 -> (Type1 or Type2)--not using Either or some "wrapper" ADT

--Update--------

Based on alot of research and help here, it's become clear to me now that no matter how many extensions I enable, the return type of a function can never be calculated based on expresions at the value level in Haskell. So I'm posting more of my actual code in hopes that someone will help me decide on the best way to move forward. I'm writing a small library with conic curves and quadric surfaces as the basic types. The operations on these types involves calculating the intersections between them. The intersection of 2 surfaces is one of the types of conic curves, including degenerates like a point(there actually needs to be another type of curve besides the conics, but that besides the point). The exact curve return type can only be determined by the values of the intersecting surfaces at run time. A Cylinder - Plane intersection can result in Nothing, Line, Circle or Ellipse. My first plan was to structure the curves and surfaces using ADT's like this...

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)

...which is the most straight forward and has the advantage of being a nice closed algebraic type, which I like. In this representation the return type of the intersection is easy, it's just Curve. The downside of this representation is that every function of these types has to pattern match for each type and handle all the permutations which seems cumbersome to me. The Surface-Surface intersection function would have 16 patterns to match on.

The next option is to keep each Surface and Curve type individual. Like so,

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)

This seems like it might be more flexible in the long run and is nice and granular but would require a wrapper ADT to be able to handle the multiple return types from the intersection function or to build a list of general "Curves" or "Surfaces" because there is no relationship between them. I could use Type Classes and existentials to group them, but then I lose the original type, which I don't like.

The compromises in these designs led me to try this..

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

...which at first I thought was going to give me some magical, best of both worlds scenario where I could refer to any curve type by "Curve" (like in a list or the intersection return type) and also have the full type available (Curve CrvIdx) to write functions in the granular style using multiparameter type classes, etc.. I soon discovered that this does not work quite as nicely as I hoped as is shown in this question. I have stubbornly continued to bang my head against the wall trying to find a way to write a function that can choose a return type from my GADT based on the geometric properties of the arguments at runtime and now realize this is not going to happen. So now the question is what would be an efficient and flexible way to represent these types and the interactions between them? Thanks.


回答1:


Short answer: no. You need to use a wrapper ADT, Data.Dynamic or a type-family/associated type.

Type families are probably the closest thing to what you want, but again, the type needs to be able to be decided at compile time. For example:

{-# 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

Here, the mixColors function can essentially return values of any type, but the return type needs to be an instance of the type family MixedColor so that the compiler can infer the actual return type based on the argument types.

You can use type families and type-classes to build relatively complex type-functions, getting you closer and closer to the functionality of dependent types, but that means your data needs to be decorated with enough type-level information to make the required type-calculations.

The recently introduced type-level natural numbers can be useful if you need to encode numerical calculations in your types.

EDIT: Also, I'm not sure why you are reluctant to use ADTs (maybe you need to describe your use-case in more detail?), because encoding e.g. the fact that a function can return either Type1 or Type2 is exactly the kind of information that ADTs encode very naturally and are idiomatically used for.



来源:https://stackoverflow.com/questions/14949021/return-type-as-a-result-of-term-or-value-calculation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!