逻辑上,可以定义通用转换功能,可以从任何类型转换为任何类型。
可能的方法是:
{-#LANGUAGE MultiParamTypeClasses #-}
{-#LANGUAGE FlexibleInstances #-}
class FromTo a b where
fromTo:: a->b
instance FromTo a a where fromTo = id
instance FromTo Int Double where fromTo = fromIntegral
instance FromTo Int Float where fromTo = fromIntegral
instance FromTo Integer Double where fromTo = fromIntegral
instance FromTo Integer Float where fromTo = fromIntegral
instance FromTo Double Int where fromTo = round
instance FromTo Double Integer where fromTo = round
instance FromTo Float Int where fromTo = round
instance FromTo Float Integer where fromTo = round
-- e.t.c.
嗯,它可以工作,可以扩展。但它非常笨重,因为我必须列出我想要使用的任何案例。
对此有什么好的解决方案吗?
如果这是正确的(但它不是),那么整齐的解决方案可以像这样完成:
{-#LANGUAGE MultiParamTypeClasses #-}
{-#LANGUAGE FlexibleInstances #-}
{-#LANGUAGE InstanceSigs #-}
class FromTo a b where
fromTo:: a->b
instance (Integral a, Num b) => FromTo a b where
fromTo::a->b
fromTo x = (fromIntegral x)
{---Commented, because addition breaks program.-------------------------------
instance (RealFrac a, Integral b) => FromTo a b where
fromTo::a->b
fromTo x = (round x)
-}
如果有一些类型集扩展(类似Haskell的伪代码)可能会有可能:
{-#LANGUAGE MultiParamTypeClasses #-}
{-#LANGUAGE FlexibleInstances #-}
{-#LANGUAGE InstanceSigs #-}
{-#LANGUAGE TypeSets #-}
class FromTo a b where
fromTo:: a->b
instance setfrom (Integral a, Num b). (Integral a, Num b) => FromTo a b where
fromTo::a->b
fromTo x = (fromIntegral x)
instance setfrom (RealFrac a, Integral b). (RealFrac a, Integral b) => FromTo a b where
fromTo::a->b
fromTo x = (round x)
setfrom C1 a.
这里应该使用来自C1
类的实例信息来定义类型集。编译器应检查实例是否相交。此扩展的另一种可能结构是set (T1,T2,...,TN) a.
,它只允许定义类型集。
UPD 1
第一种解决方案可以通过这种方式进行改进(但不正确方式):
{-#LANGUAGE MultiParamTypeClasses #-}
{-#LANGUAGE FlexibleInstances #-}
class FromTo a b where
fromTo:: a->b
instance FromTo a a where fromTo = id
instance Num b => FromTo Int b where
fromTo x = fromIntegral x
instance Num b => FromTo Integer b where
fromTo x = fromIntegral x
instance Integral b => FromTo Float b where
fromTo x = round x
instance Integral b => FromTo Double b where
fromTo x = round x
但它仍然不好,此外,在交互模式下调用时会重叠:
*Main> fromTo (10::Double) ::Double
<interactive>:108:1:
Overlapping instances for FromTo Double Double
arising from a use of `fromTo'
Matching instances:
instance FromTo a a -- Defined at 4.hs:8:10
instance Integral b => FromTo Double b -- Defined at 4.hs:19:10
In the expression: fromTo (10 :: Double) :: Double
In an equation for `it': it = fromTo (10 :: Double) :: Double
答案 0 :(得分:4)
据我所知,您希望通过类型约束来参数化类实例。现代GHC扩展可以实现这一点:
{-#LANGUAGE MultiParamTypeClasses, FlexibleInstances, InstanceSigs, ConstraintKinds,
KindSignatures, DataKinds, TypeOperators, UndecidableInstances, GADTs #-}
import GHC.Prim(Constraint)
class ConstrainedBy (cons :: [* -> Constraint]) (t :: *) where
instance ConstrainedBy '[] t
instance (x t, ConstrainedBy xs t) => ConstrainedBy (x ': xs) t
此类的目的是允许对FromTo
类中的单个类型进行多个约束。例如,您可以确定Num a, Real a => Floating a
具有与Num a => Floating a
不同的实例(这是一个人为的示例 - 但根据您的使用情况,您可能需要此功能)。
现在我们使用GADT将此类提升到数据级别:
data ConsBy cons t where
ConsBy :: ConstrainedBy cons t => t -> ConsBy cons t
instance Show t => Show (ConsBy cons t) where
show (ConsBy t) = "ConsBy " ++ show t
然后,FromTo
类:
class FromTo (consa:: [* -> Constraint]) (a :: *) (consb :: [* -> Constraint]) (b :: *) where
fromTo :: ConsBy consa a -> ConsBy consb b
我不相信有一种方法可以获得为函数fromTo
指定的类型;如果类型只是a -> b
,则无法从函数参数中推导出约束。
你的实例:
instance (Integral a, Num b) => FromTo '[Integral] a '[Num] b where
fromTo (ConsBy x) = ConsBy (fromIntegral x)
instance (RealFrac a, Integral b) => FromTo '[RealFrac] a '[Integral] b where
fromTo (ConsBy x) = ConsBy (round x)
不幸的是,你必须两次陈述所有约束。然后:
>let x = ConsBy 3 :: Integral a => ConsBy '[Integral] a
>x
ConsBy 3
>fromTo x :: ConsBy '[Num] Float
ConsBy 3.0
您可以拥有通常被视为“重叠”的实例:
instance (Integral a, Eq b, Num b) => FromTo '[Integral] a '[Num, Eq] b where
fromTo (ConsBy x) = ConsBy (fromIntegral x + 1) -- obviously stupid
>let x = ConsBy 3 :: Integral a => ConsBy '[Integral] a
>fromTo x :: Num a => ConsBy '[Num] a
ConsBy 3
>fromTo x :: (Num a, Eq a) => ConsBy '[Num, Eq] a
ConsBy 4
另一方面,如果您希望断言只有一个实例可以匹配类型和约束的组合(使上述情况不可能),您可以使用函数依赖关系来执行此操作:
{-# LANGUAGE FunctionalDependencies #-}
class FromTo (consa:: [* -> Constraint]) (a :: *) (consb :: [* -> Constraint]) (b :: *)
| consa a -> consb b, consb b -> consa a
where
fromTo :: ConsBy consa a -> ConsBy consb b
现在我编写的第三个实例无效,但是,您可以在没有显式类型注释的情况下使用fromTo
:
>let x = ConsBy 3 :: Integral a => ConsBy '[Integral] a
>fromTo x
ConsBy 3
>:t fromTo x
fromTo x
:: Num b =>
ConsBy ((':) (* -> Constraint) Num ('[] (* -> Constraint))) b
如您所见,输出类型Num b => b
是从输入类型推断出来的。这对多态和具体类型的作用相同:
>let x = ConsBy 3 :: ConsBy '[Integral] Int
>:t fromTo x
fromTo x
:: Num b =>
ConsBy ((':) (* -> Constraint) Num ('[] (* -> Constraint))) b
答案 1 :(得分:2)
一种解决方案是使用Template Haskell。您仍然需要显式添加所有类型(而不是像类型类所暗示的类型集),但它会更短:
{-# LANGUAGE TemplateHaskell #-}
-- ...
let x = mkName "x"
list = [ (''Double, ''Int, 'floor)
, (''Float, ''Int, 'floor)
]
mkI tyA tyB op =
instanceD
(cxt [])
(appT (appT (conT ''FromTo) (conT tyA)) (conT tyB))
[ funD 'fromTo [clause [] (normalB iOp) []]
, pragInlD 'fromTo Inline FunLike AllPhases
]
in sequence [ mkI a b op | (a,b,op) <- list ]
通过上述(未经测试的)模板Haskell拼接,您可以枚举成对的类型和转换操作。这仍然需要您在列表中键入n choose 2
对。
或者,您可以拥有源类型列表(tyA)和单独的目标类型列表(tyB),然后在所有源类型和所有目标类型之间进行转换 - 此策略对于类似类型(所有浮动到所有积分)并保存一些打字,但对于所有转换都不够通用。