Haskell 类型家族型黑客

Haskell 类型家族型黑客,haskell,types,Haskell,Types,我正在尝试编写一个相当多态的库。我遇到了一个很容易表现出来的情况。看起来有点像这样: {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} import Data.Map (Map) import qualified Data.Map as Map class Format f where type Target f class Form

我正在尝试编写一个相当多态的库。我遇到了一个很容易表现出来的情况。看起来有点像这样:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Map (Map)
import qualified Data.Map as Map

class Format f where type Target f
class Format f => Formatter x f where
  target :: forall y. Formatable y => Target f -> x -> y
class Formatable y where name :: y -> String

instance Formatable Integer where name = show
instance Formatable Int where name = show

split :: forall x f. (Format f, Formatter x f) => x -> f -> String -> [Either String (Target f)]
split = undefined

display :: forall x f. (Format f, Formatter x f) => f -> String -> x -> String
display f str x = let
  chunks = split x f str
  built = foldr apply "" chunks
  apply (Left s) accum = accum ++ s
  apply (Right t) accum = accum ++ name (target t x)
  in foldr apply "" chunks
class Formatter x where
    type Format x
    target :: Formatable y => Format x -> x -> y
本质上,我们有多态的
格式
s,它定义了许多
目标
s。还有许多
Formattable
对象,它们知道如何响应一系列不同的格式选项(这里简化为
name

这些
格式表
以多种方式组成,可以响应许多不同的目标<代码>格式化程序本质上是
Format
Formattable
之间的路由器——给定目标(来自特定格式),它们用合适的
Formattable
对象进行响应

这一切都很抽象。下面是一个例子:

  • DateFormat
    指定诸如
    等目标
  • MonthType
    Formattable
    Int的新类型,其名称如“二月”
  • 还有一个简单的
    实例Formattable Int,其中name=show
  • DateTime
    可能是
    (Int,MonthType,Int)
    的类型同义词
(很明显,我在这里删掉了很多机械,比如管道输送正确的值,但你明白了。)

显示功能相当简单。它接受一个格式化程序、一个指定格式的字符串、一个要显示的对象,并将其全部呈现为一个字符串

首先,它将字符串分解为目标和字符串。例如,日期格式化程序可能会将字符串
%Y-%m-%d”
分解为
[右年,左“-”、右月,左“-”、右日]
split
函数就是这样做的,并且在这里进行了编辑

display
函数只需跟踪每个目标的
Formattable
s并累加字符串

或者,至少应该是这样

但它无法进行类型检查,出现以下错误:

Reduced.hs:20:16:
    Could not deduce (Target f ~ Target f0)
    from the context (Format f, Formatter x f)
      bound by the type signature for
                 display :: (Format f, Formatter x f) => f -> String -> x -> String
      at Reduced.hs:(19,5)-(24,30)
    NB: `Target' is a type function, and may not be injective
    Expected type: [Either [Char] (Target f0)]
      Actual type: [Either String (Target f)]
    In the return type of a call of `split'
    In the expression: split x f str
    In an equation for `chunks': chunks = split x f str
Failed, modules loaded: none.

我一辈子都不知道为什么。我做错了什么?

问题是
目标f
不能确定
f
,这意味着函数

target :: (Formatter f x, Formatable y) => Target f -> x -> y
永远不能被称为。无论您为
目标
提供什么类型的注释,您都无法确定
f
是什么,因此编译器永远无法确定要使用哪个
格式化程序
实例。我不是100%确定,但解决方案可能不是使用多参数类型类,而是让
x
f
中的一个作为另一个的函数。另外,您可能应该完全删除
格式
类(您知道不需要类来使用类型族吗?)。也许是这样的:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Map (Map)
import qualified Data.Map as Map

class Format f where type Target f
class Format f => Formatter x f where
  target :: forall y. Formatable y => Target f -> x -> y
class Formatable y where name :: y -> String

instance Formatable Integer where name = show
instance Formatable Int where name = show

split :: forall x f. (Format f, Formatter x f) => x -> f -> String -> [Either String (Target f)]
split = undefined

display :: forall x f. (Format f, Formatter x f) => f -> String -> x -> String
display f str x = let
  chunks = split x f str
  built = foldr apply "" chunks
  apply (Left s) accum = accum ++ s
  apply (Right t) accum = accum ++ name (target t x)
  in foldr apply "" chunks
class Formatter x where
    type Format x
    target :: Formatable y => Format x -> x -> y

我还是被卡住了。即使我完全省略了类型族和
格式
类(并且只使用
类Ord f=>格式化程序xf,其中target::forall y.f->x->y
),它也会失败,并出现以下情况:Reduced.hs:23:40:约束中不明确的类型变量
y0:(可格式化的y0)由于使用了
name'可能的修复:在
(++)的第二个参数中添加一个类型签名来修复这些类型变量,即在表达式中
name(target t x)':acum++name(target t x)中的`应用':apply(Right t)acum=acum++name(target t x)@So8res该错误是无法内联读取的,但至少看起来是一个新错误。也许会提出一个新问题?人力资源管理。它是。我道歉。我可以通过为所有可格式化的
s创建一个框数据类型来绕过这个错误。虽然不是最优雅的解决方案,但它暂时可以使用。@So8res而不是
type Format x
。您可能希望使用
数据目标x
。数据族(与您目前使用的类型族相反)是内射的,这意味着编译器可以从
Target f
推断
f