F# F中API包装器的功能建模#

F# F中API包装器的功能建模#,f#,functional-programming,F#,Functional Programming,我试图学习F#和函数式编程,但不确定如何对以下场景建模 我正试图在F#中围绕XML API编写一个简单的包装器。此API要求我首先使用用户名和密码进行“登录”API调用。然后,API返回用于验证后续调用的会话令牌。在某个时候,会话将过期,我需要再次“登录” 在一个面向对象的项目中,我会创建一个ApicClient类。它将用户名和密码作为构造函数参数,为每个API调用公开一个公共方法,并在内部处理登录过程。会话令牌和状态可以对消费应用程序完全隐藏,并且会话令牌将在ApiClient实例存在时一直保

我试图学习F#和函数式编程,但不确定如何对以下场景建模

我正试图在F#中围绕XML API编写一个简单的包装器。此API要求我首先使用用户名和密码进行“登录”API调用。然后,API返回用于验证后续调用的会话令牌。在某个时候,会话将过期,我需要再次“登录”

在一个面向对象的项目中,我会创建一个ApicClient类。它将用户名和密码作为构造函数参数,为每个API调用公开一个公共方法,并在内部处理登录过程。会话令牌和状态可以对消费应用程序完全隐藏,并且会话令牌将在ApiClient实例存在时一直保持

我不确定如何以功能性的方式组织代码,以获得类似的结果。如果我没有创建ApiClient类,我应该将内部会话令牌数据存储在哪里?显然,我可以在F#中创建一个ApiClient类,但我希望了解如何更有效地实现这一点


谢谢你的见解。

我已经对我的答案进行了大量修改,试图将这个过程浓缩到最基本的要点,让你开始

首先要做的是设计一个系统来处理会话状态。最简单的方法是创建一个类型来包含只能在库中创建的会话数据,并创建第二个类型(即有区别的联合)来处理会话数据的验证

type SessionData internal (id : System.Guid, creationTime : System.DateTime) = 
    member this.UniqueID  = id
    member this.CreationTime = creationTime

type SessionState<'a> =
    |Valid of 'a * SessionData
    |Invalid
当我们调用API时,我们将更新会话状态映射,从旧会话状态映射到新会话状态,检查过程中的有效性

let private updateSession result (sessionState : SessionData) =
    let loginTimeSpan = sessionState.CreationTime - System.DateTime.UtcNow
    match loginTimeSpan < maxLoginSpan with
    |true -> Valid <| (result, SessionData(System.Guid.NewGuid(), System.DateTime.UtcNow))
    |false -> Invalid
如果调用成功,我们将获得包含新会话和结果的新有效状态


如果希望完全隐藏交换会话状态信息的过程,可以创建一个计算表达式来处理该过程。一个非常简单的例子是:

 type XMLAPIBuilder() =
     member this.Bind (x, f) =
        fun sessionState ->
             match x sessionState with
             |Valid (call, newSessionState) -> f call newSessionState
             |Invalid -> Invalid

     member this.Return(x) =
         fun sessionState -> Valid (x, sessionState)
您可以使用计算表达式语法来使用:

let xmlapi = XMLAPIBuilder()
let apiCalls = 
    xmlapi {
       let! a = apiCall1 args1
       let! b = apiCall2 args2
       let! c = apiCall3 args3
       return (a, b, c) // result of several api calls
    }
let result = apiCalls session // pass in session data once to retrieve results
要完全删除会话数据,我们可以创建一个不包含会话状态的新类型和一个
run
函数:

type APICallState<'a> =
    |CallSuccess of 'a
    |CallFailed

let run username password apiExpression =
    match testmagic.login "" "" with
    |Valid (_, state) -> 
        match apiExpression state with
        |Valid (result, _) -> CallSuccess result
        |Invalid -> CallFailed
    |Invalid -> CallFailed
类型APICallState
将表达式状态与匹配
|有效(结果)->CallSuccess结果
|无效->调用失败
|无效->调用失败
这允许我们设计一个复杂的表达式,对API进行一系列相关调用。我们提供用户名和密码一次,然后拨打所有电话


对于新手来说,计算表达式肯定是语言中不太容易访问的部分之一,因此我建议查看一些教程,例如:

可能有一个IO evaluator函数来处理会话内容?这听起来与本文非常吻合:感谢您的全面介绍。我正在努力。
let xmlapi = XMLAPIBuilder()
let apiCalls = 
    xmlapi {
       let! a = apiCall1 args1
       let! b = apiCall2 args2
       let! c = apiCall3 args3
       return (a, b, c) // result of several api calls
    }
let result = apiCalls session // pass in session data once to retrieve results
type APICallState<'a> =
    |CallSuccess of 'a
    |CallFailed

let run username password apiExpression =
    match testmagic.login "" "" with
    |Valid (_, state) -> 
        match apiExpression state with
        |Valid (result, _) -> CallSuccess result
        |Invalid -> CallFailed
    |Invalid -> CallFailed