【发布时间】:2019-03-01 07:55:54
【问题描述】:
Continuing my rediscovery 的免费 monad,我决定将我正在编写的一个小程序转换为使用免费 monad。
该程序收集通过 MQTT 从我拥有的一些空气传感器接收到的消息,并将结果写入数据库。我还没有实现任何错误处理,稍后会出现。
open System
open uPLibrary.Networking.M2Mqtt
open uPLibrary.Networking.M2Mqtt.Messages
open MySql.Data.MySqlClient
let connString = "server=...."
type agentMessage =
|Mqtt of string * DateTime
|ExitCode
|WaitForExit of AsyncReplyChannel<unit>
|NotifyDbReady
type agentState =
{
MqttList : (string * DateTime) list
ReplyOpt : AsyncReplyChannel<unit> option
DbReady : bool
}
type SensorReading =
{
SensorName:string
SensorDate:string
ReadingName:string
ReadingValue:string
}
let getSensorReading (s:string, dt:DateTime) =
match s.Split("@") with
|ar when ar.Length = 2 ->
ar.[1].Split("|")
|> Array.choose (fun r ->
match r.Split(":") with
|sar when sar.Length = 2 ->
{
SensorName = ar.[0]
SensorDate = dt.ToString("yyyy-MM-dd HH:mm:ss")
ReadingName = sar.[0]
ReadingValue = sar.[1]
} |> Some
|sar ->
printfn "Reading string [%s] has %i values" r sar.Length
None
) |> Some
|ar ->
printfn "Message [%s] has %i values" s ar.Length
None
let getSensorReadings lst = lst |> List.toArray |> Array.choose getSensorReading |> Array.collect id
let getLoadReadingsSql readings =
readings |> Array.map (fun r ->
sprintf "('%s', '%s', '%s', %s)" r.SensorName r.SensorDate r.ReadingName r.ReadingValue
) |> String.concat ", "
|> sprintf "insert into sensor_reading_staging(sensor_name, sensor_date, reading_name, reading_value) values %s"
|> (fun sql ->
MySqlHelper.ExecuteNonQuery(connString, sql) |> ignore
MySqlHelper.ExecuteNonQuery(connString, "call process_sensor_reading_staging()") |> ignore
)
let agent = MailboxProcessor.Start (fun inbox ->
let processSensorReadingList lst =
getSensorReadings lst
|> getLoadReadingsSql
inbox.Post NotifyDbReady
let rec messageLoop oldState = async{
let! msg = inbox.Receive()
let newState =
match msg with
|Mqtt (s, dt) when oldState.DbReady ->
async{(s, dt) :: oldState.MqttList |> processSensorReadingList } |> Async.Start
{oldState with MqttList = []; DbReady = false}
|Mqtt (s, dt) ->
{oldState with MqttList = (s, dt) :: oldState.MqttList}
|NotifyDbReady when oldState.MqttList.Length > 0 ->
async{oldState.MqttList |> processSensorReadingList } |> Async.Start
{oldState with MqttList = []; DbReady = false}
|NotifyDbReady ->
{oldState with DbReady = true}
|WaitForExit rep ->
{oldState with ReplyOpt = Some rep}
|ExitCode ->
oldState.ReplyOpt |> Option.map (fun rep -> rep.Reply()) |> ignore
{oldState with ReplyOpt = None}
return! messageLoop newState
}
messageLoop {MqttList = []; ReplyOpt = None; DbReady = true}
)
[<EntryPoint>]
let main argv =
let client = new MqttClient(argv.[0])
printfn "Connecting to broker"
client.Connect(Guid.NewGuid().ToString()) |> ignore
client.Subscribe([|"AirSensorReadings"|], [|MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE|]) |> ignore
use __ =
client.MqttMsgPublishReceived
|> Observable.subscribe(fun evArgs -> (evArgs.Message |> Array.map char |> String.Concat, DateTime.Now) |> Mqtt |> agent.Post)
agent.PostAndReply (fun rep -> WaitForExit rep)
//the agent never actually receives the exit message
0 // return an integer exit code
为此,我正在关注Mark Seemann's excellent blog post。
所以我想我需要使用返回类型为单位的每个函数,并将其推送到解释器。
这好像是
- 从 MQTT 接收消息
- 写入数据库
- 邮寄给代理
前两个似乎很容易。最后一个让我有点摸不着头脑。 这个练习产生的 AST 本质上是一个顺序的指令列表。没有什么并发的。但是发布到代理的消息可能随时发生。 那么如何结合这两个概念呢?会不会是每条收到的消息都会生成一个新的 AST,最终产生一个纯单元?换句话说,只有上面列表中的第 2 项被建模为指令。 或者我是否会为代理和碰巧写入数据库的工作完全独立的 AST?
【问题讨论】:
标签: concurrency f# monads abstract-syntax-tree