使用一组默认值初始化模块(elm 或 haskell)

Initializing a module with set of defaults (elm or haskell)

假设我们有一个模块负责向 Flickr 发出 api 请求。我不想将 api 键硬编码到 Flickr 模块中。 Api 密钥可以通过 ajax 请求获得。

目前 Flickr 模块中的每个函数都接受 apiKey 作为其参数。但是,将 api 键传递给周围并不是那么酷。有什么简单的方法可以解决吗?或者是否可以在不传递每个函数的情况下在模块之间共享一些值。

module Flickr where

searchPhotos : String -> String -> ...
searchPhotos apiKey query = ...

getPhotoInfo : String -> String -> ...
getPhotoInfo apiKey photoId = ...

anotherOne : String -> ...
anotherOne apiKey = ...

更新: 到目前为止我尝试的是部分应用函数。我把 apiKey 之类的参数放在最后。但是现在我必须把这个功能传递给周围,还有其他想法吗?

makeFlickrRequest : (String -> String -> a) -> a
makeFlickrRequest flickrMethod = flickrMethod "myApikey" "mySecret"

photosSearch : String -> String -> String -> ...
photosSearch query apiKey secret =
    makeHTTPCallhere ...

-- Usage:
makeFlickrRequest (photosSearch "haskell")

使用 reader monad,您可以隐藏所有功能的公共 'environment'(API 键)。这是一个简单的例子:

首先,

import Control.Monad.Reader

然后,一些类型别名有助于提高可读性。这里值得注意的是 FlickrRequest a 部分 - 它代表一个 flickr 请求,其中 returns 类型为 a:

的值
type APIKey = String
type Photo = String
type PhotoInfo = String

type FlickrRequest a = Reader APIKey a

这是搜索照片和获取某些照片信息的两个虚拟实现:

searchPhotos :: String -> FlickrRequest [Photo]
searchPhotos query = do
    apiKey <- ask
    return ["<Photo for query " ++ query ++ " (api key " ++ apiKey ++ ")>"]

getPhotoInfo :: Photo -> FlickrRequest PhotoInfo
getPhotoInfo photo = do
    apiKey <- ask
    return $ "This is the photo information for photo " ++ photo ++ " (" ++ apiKey ++ ")"

请注意,API 键是通过 FlickrRequest reader 隐式传递的。在函数中,您可以使用 ask 访问该环境(您获得 'read' 环境)。当组合所有在同一环境中运行的功能时,它的美妙之处就体现出来了,例如:

-- This could be just `searchPhotos "*" >>= mapM getPhotoInfo` but I don't
-- want to obscure things unnecessarily.
allPhotoInfos :: FlickrRequest [PhotoInfo]
allPhotoInfos = do
    photos <- searchPhotos "*"
    sequence (map getPhotoInfo photos)

我们首先调用 searchPhotos,然后将 getPhotoInfo 应用于所有找到的照片。请注意 API 密钥是如何无处可见的,它是隐式传递的!

最后,要运行整个事情,可以使用runReader功能。像

main :: IO ()
main = do
    let myAPIKey = "someAPIKey"
    print (runReader allPhotoInfos myAPIKey)

只是为了解释我的评论,我的意思是将 apikey 应用于每个函数,如下所示 example:

type ApiKey = String

apikey::ApiKey
apikey = "foo"


f1 :: ApiKey -> String -> String
f1 "foo" _  = "1"
f1 _ s =  s

f2 :: ApiKey -> String -> String
f2 "foo" _  = "2"
f2 _ s =  s

f1', f2':: (String -> String)
[f1', f2'] = map (\x-> x apikey) [f1, f2]

main = do
    putStrLn $ f1 "asdf" "2"
    putStrLn $ f1' "2"

Frerich Raabe 的解决方案非常适合 Haskell,但不幸的是,我们没有 Elm 中的 do 符号或 Reader Monad 等价物。

但是,我们有端口,我们可以使用这些端口在从 Javascript 初始化 Elm 模块时提供配置数据。

例如,您可以在 Elm 中定义一个名为 apiKey 的端口。由于端口的值来自javascript,我们只定义函数签名,不定义函数体:

port apiKey : String

在启动 Elm 模块的 HTML/javascript 文件中,您可以传递第二个包含初始端口值的参数,如下所示:

<script>
  var app = Elm.fullscreen(Elm.Main, {
    apiKey: "myApiKey"
  });
</script>

现在,在您的整个 Elm 代码中,您有一个名为 apiKey 的常量函数始终可用。您永远不需要将它作为参数传递给其他函数。