我想在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)中执行此操作的规范方法是什么?
答案 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查找,但是可以将其建模为记录类型中的另一个字段,该字段告诉您该对象属于哪个子类。