在函数定义中使用实例模式

时间:2015-12-22 12:39:14

标签: haskell instance phantom-types

我想创建一个运算符来使用"幻像输入"单位:

var listA = new[] { 1, 2, 3 };
var listB = new[] { "a", "b", "c" };
var listC = new[] { 5f, 6f, 7f };
foreach(var n in Iterator.Enumerate(listA, listB, listC))
{
    foreach(var obj in n)
    {
        Console.Write(obj.ToString() + ", ");
    }
    Console.WriteLine();
}

为避免重复每个单元的实例声明,我想在实例声明中使用newtype Length (a::UnitLength) b = Length b deriving (Eq) data UnitLength = Meter | KiloMeter | Miles deriving (Eq,Show) class OperAdd a b c where (<+>) :: a -> b -> c instance Num val => OperAdd (Length a val) (Length b val) (Length c val) where (<+>) (Length la) (Length lb) = if a == b then Length (la+lb) else ... ab类型来进行自动单位转换。

是否可以在我的运营商定义中使用c ab

1 个答案:

答案 0 :(得分:2)

不,因为Haskell严格区分了类型级变量和值级变量。您确定要在类型级别处理物理单位吗?物理尺寸,这确实有意义(请查看例如units包),但为什么你需要不同类型的不同单位长度,因为它们显然是等同的?好吧,这可能是有意义的避免costy隐式单位转换,但为什么你定义一个运算符,它做了或多或少的隐式单位转换呢?

无论如何,您不能使结果类型c依赖于(值级)if语句。基本上你需要写出一个类型级case /模式匹配;最直接的方法是使用多个实例:

instance  Num n => OperAdd (Length Metre n) (Length Metre n) (Length Metre val) where
  (<+>) (Length  la) (Length  lb) = Length (la+lb)
instance  Num n => OperAdd (Length Metre n) (Length KiloMetre n) (Length Metre n) where
  (<+>) (Length  la) (Length lb') = Length (la+lb*1000)
instance  Num n => OperAdd (Length Metre n) (Length Metre n) (Length KiloMetre n) where
  (<+>) (Length  la) (Length  lb) = Length $ (la+lb)/1000
...

singletons可能会出现更通用的解决方案,与原始想法更相似。