haskell `Reader a(b -> c)`和`b -> Reader a c`之间有什么区别吗?

1tu0hz3e  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(93)

(Some其他的mtl monad也会带来同样的问题,但是让我们在这里使用Reader来解决这个问题。
假设我需要以一种人类可读的方式显示一些东西,根据用户的偏好,从配置中读取实现和行为细节:

showIt :: Config -> Object -> String

如果用户配置只是读取而不是修改,那么也可以使用Reader来反映:

showIt :: Reader Config (Object -> String)

但是等等,这和这个有什么不同:

showIt :: Object -> Reader Config String

虽然我几乎不知道Reader,但我确实尝试过这两种设计,它们在功能上似乎是等同的。我仍然想知道这两者之间是否存在语义上的差异,也就是说,当其他一些Haskellers查看我的代码时,如果我使用其他设计,他们会解释不同的目的或含义吗?如果是,那么在这样的场景中应该首选哪种用法?

f87krz0w

f87krz0w1#

Reader Config (Object -> String)Object -> Reader Config String有什么区别?
前者将showIt实现限制为read a config仅一次,然后生成一个必须对所有对象都有效的字符串转换函数。后者允许实现根据输入对象决定读取什么配置(或者更确切地说,是否读取1)。
它们的使用模式截然不同:

do
  showAny <- showIt
  let str1 = showAny object1
  let str2 = showAny object2
  return (str1 ++ " " ++ str2)

vs

do
  let show1 = showIt object1
  let show2 = showIt object2
  str1 <- show1
  str2 <- show2
  return (str1 ++ " " ++ str2)

1:使用Reader monad,没有太多的选择-您只能决定是否读取配置(或者:(我会告诉你多久读一次,但每次都是一样的。对于其他monad,阅读配置的不同位可能涉及不同的文件系统或数据库访问等,差异将变得更大。

vyswwuz2

vyswwuz22#

与另一个答案相反,这些都是同一件事。
Reader a b只是a -> bnewtype Package 器,

newtype Reader a b = Reader (a -> b)

这意味着Reader a ba -> b具有完全相同的功能--只是不同的API。因为a -> b -> c只是a -> (b -> c)的另一种写法,所以你可以看到这些都是写同一个东西的不同写法:

Reader Config (Object -> String)
Config -> (Object -> String)
Config -> Object -> String

你的最后一个例子在道德上仍然和其他例子一样。函数接受参数的顺序是无关紧要的-你可以很容易地把它写成相反的方式:

Object -> Reader Config String
Object -> (Config -> String)
Object -> Config -> String
Config -> Object -> String  -- flip the arguments

使用Reader的代码碰巧是以某种风格编写的(也就是说,使用do表示法,runReader和朋友),但这实际上只是一个分心-这是我们真正关心的行为。
当然还有其他(非Reader)单子,这些类型是不等价的,但这是一个不同的问题!

相关问题