Haskell中的通用型变压器

时间:2013-11-28 16:30:22

标签: haskell ghc

逻辑上,可以定义通用转换功能,可以从任何类型转换为任何类型。

可能的方法是:

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

2 个答案:

答案 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),然后在所有源类型和所有目标类型之间进行转换 - 此策略对于类似类型(所有浮动到所有积分)并保存一些打字,但对于所有转换都不够通用。