2

下面的函数 f,对于给定的类型“a”,采用类型“c”的参数。对于不同类型的“a”,“c”以不同的方式进行限制。具体来说,当“a”是任何 Integral 类型时,应该允许“c”是任何“Real”类型。当“a”是浮点数时,“c”只能是浮点数。

一种尝试是:

{-# LANGUAGE
MultiParamTypeClasses,
FlexibleInstances,
FunctionalDependencies,
UndecidableInstances #-}

class AllowedParamType a c | a -> c

class Foo a where
    f :: (AllowedParamType a c) => c -> a

fIntegral :: (Integral a, Real c) => c -> a
fIntegral = error "implementation elided"

instance (Integral i, AllowedParamType i d, Real d) => Foo i where
    f = fIntegral

出于某种原因,GHC 7.4.1 抱怨它“无法推断出使用 fIntegral 引起的 (Real c)”。在我看来,功能依赖应该允许这种推论。在实例中,a 与 i 统一,因此通过功能依赖,d 应该与 c 统一,在实例中声明为“Real”。我在这里想念什么?

抛开功能依赖不谈,这种方法的表达能力是否足以强制执行上述限制,还是有更好的方法?我们只为 'a' 使用几个不同的值,因此会出现以下情况:

instance (Integral i, Real c) => AllowedParamType i c
instance AllowedParamType Float Float

谢谢

4

3 回答 3

3

一种可能更好的方法是使用约束种类和类型系列(我认为 GHC 扩展,需要 GHC 7.4)。这允许您将约束指定为类实例的一部分。

{-# LANGUAGE ConstraintKinds, TypeFamilies, FlexibleInstances, UndecidableInstances #-}

import GHC.Exts (Constraint)

class Foo a where
   type ParamConstraint a b :: Constraint
   f :: ParamConstraint a b => b -> a

instance Integral i => Foo i where
   type ParamConstraint i b = Real b
   f = fIntegral

编辑:经过进一步的实验,有一些细微之处意味着这不能按预期工作,特别type ParamConstraint i b = Real b是过于笼统。我现在不知道解决方案(或者是否存在)。

于 2012-09-13T07:58:09.337 回答
1

好吧,这个一直在唠叨我。鉴于实例种类繁多,让我们全力以赴,摆脱源类型和目标类型之间的任何关系,而不是实例的存在:

{-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}

class Foo a b where f :: a -> b

现在我们可以将类型对与f它们之间的类型进行匹配,但是我们喜欢,例如:

instance Foo Int Int where f = (+1)
instance Foo Int Integer where f = toInteger.((7::Int) -)
instance Foo Integer Int where f = fromInteger.(^ (2::Integer))
instance Foo Integer Integer where f = (*100)
instance Foo Char Char where f = id
instance Foo Char String where f = (:[])  -- requires TypeSynonymInstances
instance (Foo a b,Functor f) => Foo (f a) (f b) where f = fmap f -- requires FlexibleInstances
instance Foo Float Int where f = round
instance Foo Integer Char where f n = head $ show n

这确实意味着要避免大量显式类型注释No instance for...Ambiguous type错误消息。例如,你不能做main = print (f 6),但你可以做main = print (f (6::Int)::Int)

您可以使用所需的标准类型列出所有实例,这可能会导致大量重复,您可以点亮蓝色触摸纸并执行以下操作:

instance Integral i => Foo Double i where f = round -- requires FlexibleInstances
instance Real r => Foo Integer r where f = fromInteger -- requires FlexibleInstances

注意:这并不意味着“嘿,如果你有一个整数类型,你可以使用这个方便的圆形函数免费i拥有一个实例”,它的意思是:“每次你有任何类型,它肯定是一个实例 。通过方式,我正在使用这个,所以除非你的类型是,否则我们会失败。” 例如,对于该实例来说,这是一个大问题。Foo Double iiFoo Double iroundiIntegralFoo Integer Char

这很容易破坏你的其他实例,所以如果你现在输入f (5::Integer) :: Integer你会得到

Overlapping instances for Foo Integer Integer
  arising from a use of `f'
Matching instances:
  instance Foo Integer Integer
  instance Real r => Foo Integer r

您可以更改您的编译指示以包含 OverlappingInstances:

{-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}

所以现在f (5::Integer) :: Integer返回 500,很明显它使用的是更具体的Foo Integer Integer实例。

我认为这种方法可能对您有用,手动定义许多实例,仔细考虑何时完全疯狂地从标准类型类中创建实例。(或者,标准类型并不多,众所周知,notMany choose 2 = notIntractablyMany,因此您可以将它们全部列出。)

于 2012-09-18T20:16:46.427 回答
0

这是一个解决更普遍问题的建议,而不是您的具体问题(我首先需要更多细节 - 我保证稍后再检查)。我正在写它以防其他人正在寻找与您类似的问题的解决方案,我当然在过去,在我发现 SO 之前。当它可以帮助您尝试一种全新的方法时,SO 特别棒。

我以前的工作习惯是:

  1. 引入一个多参数类型类(类型到处都是,所以......)
  2. 引入功能依赖(应该整理一下,但我最终需要......)
  3. 添加 FlexibleInstances(警钟开始响起。编译器默认关闭此功能是有原因的......)
  4. 添加 UndecidableInstances(GHC 告诉你你是靠你自己的,因为它不相信它可以应对你设置的挑战。)
  5. 一切都炸了。以某种方式重构。

然后我发现了类型族的乐趣(类型的函数式编程(万岁)——多参数类型类(有点像)类型的逻辑编程)。我的工作流程更改为:

  1. 引入包含关联类型的类型类,即替换

    class MyProblematicClass a b | a -> b where
      thing :: a -> b
      thang :: b -> a -> b
    

    class MyJustWorksClass a where
      type Thing a :: * -- Thing a is a type (*), not a type constructor (* -> *)
      thing :: a -> Thing a
      thang :: Thing a -> a -> Thing a
    
  2. 紧张地添加 FlexibleInstances。根本没有任何问题。

  3. 有时通过使用约束来解决问题,例如(MyJustWorksClass j,j~a)=>代替(MyJustWorksClass a)=>(Show t,t ~ Thing a,...)=>代替(Show (Thing a),...) =>来帮助 ghc。(~本质上是指'与'同类型')
  4. 紧张地添加 FlexibleContexts。根本没有任何问题。
  5. 一切正常。

“什么都没有出错”的原因是 ghc使用我的类型函数计算类型,而不是尝试仅使用一堆断言来推断它,即那里有一个函数并且它应该能够解决它。Thing aThang

搏一搏!在阅读手册之前阅读Fun with Type Functions

于 2012-09-13T15:37:57.420 回答