尝试 return 可扩展记录时出现编译类型错误
Compile type error when trying to return Extendable record
我已经创建了一个可扩展的记录类型,我如何才能 return 最小基类型而不让编译器抛出错误。
我有一个类型的记录,
type SectionContent r =
{ titleText :: String
, subTitleText :: String
, ....
| r
}
我正在扩展它,例如
type MyConfig =
{ section1 : SectionContent ()
, section2 : SectionContent (someContent :: {...})
..
}
当我尝试像这样访问我的代码时,
-- This is inside another function & I have access to config
currenScreenConfig :: forall r. SectionContent r
currenScreenConfig = if predicate
then config.section1
else config.section2
抛出错误,
Could not match type
( someContent :: {...})
with type
r0
.....
where r0 is a rigid type variable
bound at ...
....
这里的 rigid 类型到底是什么意思?
这是关于谁可以选择通用参数的经典混淆。
当您访问(或“引用”或“使用”)其类型前面带有 forall r.
的值时,您可以选择 r
是什么。实施该值的人必须使其能够与您选择的 r
一起使用。事先不知道你会选择什么
换句话说:选择类型参数的是函数的调用者,而不是实现者。
因此,稍后调用您的 currenScreenConfig
函数的人可能会决定选择 r ~ ( foo :: String )
。然后你的函数必须 return 一条记录 SectionContent ( foo :: String )
.
另一个调用者,在不同的地方,可能会选择 ( bar :: Int )
,然后你的函数必须 return 一条记录 SectionContent ( bar :: Int )
.
你知道怎么没有办法实现这样的功能吗?
现在,如果您只想 return SectionContent ()
,那么函数的类型应该是:
currenScreenConfig :: SectionContent ()
当然你不能 return config.section2
,因为它有不同的类型。
有一些方法可以“trim”一条记录(即丢弃不需要的字段)。一种这样的方法是 record-extra
包中的 the pick
function,您可以像这样使用它:
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1 else pick config.section2
但是,我鼓励您改用更自然的模型。如果各种屏幕内容的“公共”部分像那样被普遍提取,将它作为一个字段包括而不是合并会更自然:
type SectionContentCommon =
{ titleText :: String
, subTitleText :: String
, ....
}
type SectionContent r =
{ common :: SectionContentCommon
| r
}
type MyConfig = ... same ...
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1.common else config.section2.common
使用可扩展记录可能看起来很酷很闪亮,但我强烈建议您考虑使用更直接的模型。相信我:炫酷和闪亮永远不会超过 long-term 维护。
我已经创建了一个可扩展的记录类型,我如何才能 return 最小基类型而不让编译器抛出错误。
我有一个类型的记录,
type SectionContent r =
{ titleText :: String
, subTitleText :: String
, ....
| r
}
我正在扩展它,例如
type MyConfig =
{ section1 : SectionContent ()
, section2 : SectionContent (someContent :: {...})
..
}
当我尝试像这样访问我的代码时,
-- This is inside another function & I have access to config
currenScreenConfig :: forall r. SectionContent r
currenScreenConfig = if predicate
then config.section1
else config.section2
抛出错误,
Could not match type
( someContent :: {...})
with type
r0
.....
where r0 is a rigid type variable
bound at ...
....
这里的 rigid 类型到底是什么意思?
这是关于谁可以选择通用参数的经典混淆。
当您访问(或“引用”或“使用”)其类型前面带有 forall r.
的值时,您可以选择 r
是什么。实施该值的人必须使其能够与您选择的 r
一起使用。事先不知道你会选择什么
换句话说:选择类型参数的是函数的调用者,而不是实现者。
因此,稍后调用您的 currenScreenConfig
函数的人可能会决定选择 r ~ ( foo :: String )
。然后你的函数必须 return 一条记录 SectionContent ( foo :: String )
.
另一个调用者,在不同的地方,可能会选择 ( bar :: Int )
,然后你的函数必须 return 一条记录 SectionContent ( bar :: Int )
.
你知道怎么没有办法实现这样的功能吗?
现在,如果您只想 return SectionContent ()
,那么函数的类型应该是:
currenScreenConfig :: SectionContent ()
当然你不能 return config.section2
,因为它有不同的类型。
有一些方法可以“trim”一条记录(即丢弃不需要的字段)。一种这样的方法是 record-extra
包中的 the pick
function,您可以像这样使用它:
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1 else pick config.section2
但是,我鼓励您改用更自然的模型。如果各种屏幕内容的“公共”部分像那样被普遍提取,将它作为一个字段包括而不是合并会更自然:
type SectionContentCommon =
{ titleText :: String
, subTitleText :: String
, ....
}
type SectionContent r =
{ common :: SectionContentCommon
| r
}
type MyConfig = ... same ...
currenScreenConfig :: SectionContent ()
currenScreenConfig = if predicate then config.section1.common else config.section2.common
使用可扩展记录可能看起来很酷很闪亮,但我强烈建议您考虑使用更直接的模型。相信我:炫酷和闪亮永远不会超过 long-term 维护。