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

时间:2018-10-27 09:28:45

标签: haskell functional-programming elm

我想在Elm中为外部api创建一个http客户端。在scala(OO / FP混合)中,我将这样表达(暂时忘记异步):

class Client(url: String) {
    def getFoo(): String = ???
}

但是在Elm Im中有点迷路了。显而易见的解决方案是将url直接传递给函数

module Client

getFoo : String -> String

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

我尝试使用具有以下功能的记录:

type alias Client = { getFoo: String }

createClient : String -> Client

但是感觉像是对OOP的模仿。在AFAIU中,这是通过Ocaml中的Functors和OOP中的对象解决的。

在Elm(如果Elm在这里缺少某些特殊功能,或者是Haskell)中执行此操作的规范方法是什么?

1 个答案:

答案 0 :(得分:5)

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

--  OO                       ┃      functional/procedural
Client c = ...;              │     c = ... :: Client
...                          │     ...
main() {print(c.getFoo());}  │     main = print(getFoo c)

因此,使用C之类的程序语言和FP语言的这条路线很可能而且通常很有用。

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

getFoo :: Client -> String
getFoo (Client{url = u}) = ...

是的,这要求您显式传递Client对象,但这并不一定是一件坏事–只要您具有正确区分的类型,则很明显需要将什么作为参数传递给什么功能,这种方法实际上比OO方法具有更好的可扩展性,因为您可以将多个对象作为参数,并且每个函数都可以采用所需的对象。

当然,在某些情况下,您确实拥有一堆都需要同一个对象的函数,并且希望将其隐藏在后台而不将其显式传递给所有对象。可以通过将其隐藏在 result 类型中来完成。

type Reader c r = c -> r

getFoo :: Reader Client String
getBar :: Reader Client Int
getBaz :: Reader Client Double

reader monad可以与标准monad组合器一起使用:

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

如果您的方法中也有 mutation (在OO中很常见),则此方法特别有用。通过显式传递,确实确实非常麻烦,因为您需要使用更新的副本,并且需要小心将正确的版本传递给每个函数。使用state monad,它会被自动处理,就像它是真正的突变一样。


在这里我忽略继承。如果您通过超类指针调用方法,则会有一个额外的vtable查找,但是可以将其建模为记录类型中的另一个字段,该字段告诉您该对象属于哪个子类。