如何在Elm(或Haskell)中的函数之间共享数据

Kre*_*ver 2 haskell functional-programming elm

我想在elm中为外部api创建一个http客户端.在scala中,这是OO/FP混合,我会表达这一点(暂时忘记异步),简单如下:

class Client(url: String) {
    def getFoo(): String = ???
}
Run Code Online (Sandbox Code Playgroud)

但在榆树林有点迷失.显而易见的解决方案是将url直接传递给函数

module Client

getFoo : String -> String
Run Code Online (Sandbox Code Playgroud)

但这使用起来非常痛苦,因为它使每个调用都变得复杂,因此负担随着定义的函数数量和对这些函数的调用次数而增加.

我试图使用带有函数的记录,例如:

type alias Client = { getFoo: String }

createClient : String -> Client
Run Code Online (Sandbox Code Playgroud)

但感觉就像是对OOP的模仿.AFAIU由Ocaml中的Functors和OOP中的对象解决.

什么是在Elm(或Haskell,如果Elm缺少某些特定功能)的规范方法?

lef*_*out 5

请记住,调用†的 OO方法只不过是为函数提供额外this/ self参数的语法糖:

--  OO                       ?      functional/procedural
Client c = ...;              ?     c = ... :: Client
...                          ?     ...
main() {print(c.getFoo());}  ?     main = print(getFoo c)
Run Code Online (Sandbox Code Playgroud)

因此,使用C语言和FP语言等过程语言,这种路径是非常有可能的,而且通常很有用.

data Client {
    url :: String
  , ...
  }

getFoo :: Client -> String
getFoo (Client{url = u}) = ...
Run Code Online (Sandbox Code Playgroud)

是的,这需要你明确地传递Client对象,但这不一定是坏事 - 如果你有正确的区分类型,可以很明显需要传递什么函数的哪个参数,这个方法实际上比OO方法更好地扩展,因为你可以有多个对象作为参数,每个函数可以只接受它需要的那些.

当然,也有情况下,你也有一大堆的所有需要同一个对象的功能,想拥有它发生在引擎盖下没有明确地传递给它无处不在.这可以通过将其隐藏在结果类型中来完成.

type Reader c r = c -> r

getFoo :: Reader Client String
getBar :: Reader Client Int
getBaz :: Reader Client Double
Run Code Online (Sandbox Code Playgroud)

此阅读器monad可与标准monad组合器一起使用:

 quun = (`runReader`c) $ do
   foo <- getFoo     -- `c` argument implicitly passed
   bar <- getBar
   baz <- getBaz
   return (calcQuun foo bar (2*baz))
Run Code Online (Sandbox Code Playgroud)

如果您的方法中也有变异,这种方法特别有用,这在OO中很常见.通过显式传递,这变得非常麻烦,因为您需要使用更新的副本,并且需要小心将正确的版本传递给每个函数.使用状态monad,这将自动处理,就像它是真正的突变一样.


† 我在这里无视遗产.如果通过超类指针调用方法,则会有一个额外的vtable查找,但可以将其建模为记录类型中的另一个字段,告诉您此对象属于哪个子类.