2015-05-29 84 views
4

我正在嘗試爲Haskell中的API創建一些綁定。我注意到一些函數有很多參數,例如與API交談的最佳實踐

myApiFunction :: Key -> Account -> Int -> String -> Int -> Int -> IO (MyType) 

有這麼多論點本身並不一定是壞的。但作爲一個用戶,我不喜歡冗長的參數功能。但是,這些參數絕對是100%必需的。

是否有更多的haskell-ish方式來抽象這些函數的公共部分?這裏的所有東西都是用來構建一個URL的,所以我需要它可用,它的含義完全取決於函數。雖然有些事情是一致的,如KeyAccount,我想知道什麼是最好的抽象這些參數是。

謝謝!

+2

聲明一個將所有這些獨立參數綁定到一個值的類型? (嘗試查找邏輯分組通常是有趣的部分。)您可能還想爲所有這些「Int」值定義一個「newtype」或至少一個「type」別名。 – MathematicalOrchid

+0

我在Strive中嘗試了一堆不同的想法,我的API綁定到Strava。我最終結束了設置類型和鏡頭。看看[這個問題](https://github.com/tfausak/strive/issues/44)的一些討論和[這個例子](https://github.com/tfausak/strive/tree/v1.0。1#細分 - 排行榜),看看它現在的樣子。 –

回答

2

您可以組合到這些更具描述性的數據類型:

data Config = Config 
    { cKey :: Key 
    , cAccount :: Account 
    } 

那麼也許有type S或newtypes使其他參數的詳細描述:

-- I have no idea what these actually should be, I'm just making up something 
type Count = Int 
type Name = String 
type Position = (Int, Int) 

myApiFunction :: Config -> Count -> Name -> Position -> IO MyType 
myApiFunction conf count name (x, y) = 
    myPreviousApiFunction (cKey conf) 
          (cAccount conf) 
          name 
          name 
          x 
          y 

如果總是需要Config,那麼我會建議在一個Reader monad中工作,你可以很容易地做

myApiFunction 
    :: (MonadReader Config io, MonadIO io) 
    => Count -> Name -> Position 
    -> io MyType 
myApiFunction count name (x, y) = do 
    conf <- ask 
    liftIO $ myPreviousApiFunction 
       (cKey conf) 
       (cAccount conf) 
       name 
       name 
       x 
       y 

這使用單數變壓器的mtl庫。如果你不希望有遍地鍵入約束,您還可以使用ConstraintKinds擴展它的別名:

{-# LANGUAGE ConstraintKinds #-} 
{-# LANGUAGE FlexibleContexts #-} 
... 

type ApiCtx io = (MonadReader Config io, MonadIO io) 

... 

myApiFunction 
    :: ApiCtx io 
    => Count -> Location -> Position 
    -> io MyType 
myApiFunction ... 

根據您的具體應用,你也可以把它分解成多個功能。我見過很多的API在此之前,有這樣的事情

withCount :: ApiCtx io => Count -> io a -> io a 
withName :: ApiCtx io => Name  -> io a -> io a 
withPos :: ApiCtx io => Position -> io a -> io a 

(&) :: a -> (a -> b) -> b 

request :: ApiCtx io => io MyType 

> :set +m -- Multi-line input 
> let r = request & withCount 1 
|     & withName "foo" 
|     & withPos (1, 2) 
> runReaderT r (Config key acct) 

這些只是技術了一把,也有其他人在那裏很好,但他們普遍開始後成爲這個更復雜。其他人對於如何做到這一點會有不同的偏好,我相信很多人會不同意我的看法,其中有些甚至是不錯的做法(特別是ConstraintKinds,這是不被普遍接受的)。

如果您發現自己的類型簽名太大了,即使在應用了這些技術之後,也許您會從錯誤的方向接近問題,也許這些功能可以分解爲更簡單的中間步驟,也許這些參數中的一些可以邏輯分組到更具體的數據類型中,也許你只需要一個更大的記錄結構來處理設置複雜的操作。它現在是非常開放的。

+0

我愛最後一個。感謝所有這些 – Steve

+0

@bhelkilr runReaderT函數如何工作? – Steve

+0

@Steve它需要一個ReaderT操作並從調用中返回值以詢問,然後使用該值運行操作。如果您以前使用過State monad,那麼將Reader monad視爲固定狀態,您只能獲取它,而不能設置它。它通常用於傳遞靜態配置。 – bheklilr