Haskell中的record field语义算是一个好设计吗?

比如经典的State monad的如下定义:newtype State s a = { runState :: s -> (a, s) }按照Haskell的语义,该声明生成了一个用于访问属性的全局函数runState :: State s a -> s -> (a, s) 可以看到两个runState字面相同,类型签名却不一样,给初学阶段带来不少疑惑。可不可以理解为这种声明是对newtype State s a = { runState :: self -> s -> (a, s) }的一种简化?
关注者
36
被浏览
1326

3 个回答

谢邀,runState不是对
newtype State s a = { runState :: self -> s -> (a, s) }
的一种简化。
在Haskell中,runState是为了访问如下定义的成员而生成到函数。
newtype State s a = { runState :: s -> (a, s) }
对应二元组(a, b)的fst和snd函数。这个参考lens的用法就更清楚了。
这个问题在于 Haskell 生成的 Record Field Selector 是全局函数。
换成这样写就很明白了:
st.runState :: s -> (a, s)
其实你在Scala、Java中签名也是这样写的,只不过它们支持命名空间 dot 语法罢了:
class MyState(val runState : Int => (String,Int));
val a = new MyState( (i) => ("Ha?",i+1) )
a.runState //  Int => (String,Int)
参见:The Limitations of HaskellTypeDirectedNameResolution - Haskell Prime
Records/OverloadedRecordFields/DuplicateRecordFields - GHC

我觉得 TypeDirectedNameResolution 才是正经解决方案,其它几个方案治标不治本。Haskell的这些缺陷确实令(人|我)非常非常不爽,另外还有 Module ,应该采用 Python 而不是 Java 的风格。既然模块的ID必须跟它的文件路径保持一致,根据 DRY(Don't Repeat Yourself) 原则,我们没必要在源文件中将这个路径再重复声明一遍,也就是说 module where 中的 modid 根本没必要存在。另外我觉得实践上来说 Package 中所有模块最好只使用一个根命名空间,最多前面再加个 Data/Text 这样的分类命名空间,Yesod就很好,而 ShakespeareResourcet 这些包就令人很困惑,看到导入一个模块,我都拿不准它到底在哪个包里,命名冲突解决起来就更麻烦了。