这个远程monad设计模式是一种封装外部一元功能。其思想是,与其直接调用远程或外部过程,相反,我们给外部过程调用一个特定于服务的单子函数类型,并使用一元“send”函数调用外部过程调用。具体来说远程单子是在中具有评估功能的单子本地运行时系统之外的远程位置。这篇博客文章是一系列文章中的第一篇,探讨了反思用于从Haskell访问外部数据的shell命令。我们将查看PlistBuddy公司OSX UNIX命令,其内部shell,以及如何为Haskell提供对此shell的访问用户。

Plists和PlistBuddy

Plist是OSX和iOS中用于存储配置数据的格式,有时用作基于文本的小型数据库。大致来说,它们是基于XML的JSON类型记录表示,其中每个Plist是一个文件,其中包含一个有时很大的XML结构。OSX和iOS应用程序使用库来读取、修改和写入Plist。

PlistBuddy是用于修改Plist的UNIX命令。PlistBuddy有一个交互式模式,我们感兴趣的是将这种交互模式反映到Haskell中。

$/usr/libexec/PlistBuddy--帮助命令格式:帮助-打印此信息退出-退出程序,更改不会保存到文件保存-将当前更改保存到文件还原-重新加载上次保存的文件版本清除[<类型>]-清除所有现有条目,并创建类型的根打印[<条目>]-打印条目的值。否则,打印文件设置<条目><值>-将条目处的值设置为值Add<Entry><Type>[<Value>]-将条目添加到plist,值为ValueDelete<Entry>-从plist中删除条目...

举个例子,我们可以创建一个Plist,填充它,检查它,然后保存它。

$/usr/libexec/PlistBuddy示例.plist文件不存在,将创建:example.plist命令:添加用户字符串“Fred Flintstone”命令:添加年龄整数39命令:打印Dict公司{年龄=39user=弗雷德·弗林斯通}命令:打印年龄39命令:保存正在保存。。。命令:退出

文件示例.plist现在是

<?xml version=“1.0”encoding=“UTF-8”?>
<!DOCTYPE plist PUBLIC“-//Apple//DTD plist 1.0//EN”http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<普利斯特 版本=“1.0”>
<字典>
	<键>年龄</key>
	<整数>39</整数>
	<键>用户</key>
	<字符串>摩登原始人</string>
</dict>
</plist>

这是一个微不足道的例子;字典和数组可以嵌套到任意深度。PlistBuddy公司健壮地负责XML文件的读取和写入。当然,我们可以写一个PlistBuddy公司-在Haskell中克隆,但这没有抓住要点:

  • PlistBuddy公司支持OSX和iOS使用的另外两种文件格式,包括二进制格式。
  • PlistBuddy公司支持现成的多兆字节plists。

通过为Haskell用户提供可用的APIPlistBuddy公司直接,我们可以快速使用强大的功能。如果在未来,plist资源最终出现在关键路径上,我们可以用本机Haskell库替换该库,或者使用FFI调用C库。

PlistBuddy远程单车

远程monad调用是远程过程调用的泛化。这个呼叫不需要通过网络;远程monad中的“远程”指定Haskell运行时系统的外部化。在这种情况下,我们正在向另一个UNIX进程发送文本命令,并接收文本返回。

我们的基本API包括(1)一个开启器,用于打开远程通道资源;(2) 的发送命令;(3)远程单子命令,使用monad调用PlistBuddy公司.

--我们的开场白
openP列表 :: 文件路径 -> IO(输入输出) 夹板

--我们的“发送”命令
发送 :: 夹板 -> PlistBuddy公司  -> IO(输入输出) 

--我们的远程单子命令
帮助   ::                    PlistBuddy公司 文本
出口   ::                    PlistBuddy公司 ()
节约   ::                    Plist伙伴 ()
恢复 ::                    PlistBuddy公司 ()
清楚的  :: 价值           -> PlistBuddy公司 ()
得到    :: [文本]          -> PlistBuddy公司 价值
设置    :: [文本] -> 价值 -> PlistBuddy公司 ()
添加    :: [文本] -> 价值 -> PlistBuddy公司 ()
删除 :: [文本]          -> PlistBuddy公司 ()

数据 价值  = 字符串 文本
            | 阵列 [价值]       
            | 词典 [(文本,价值)] 
            | 布尔 布尔
            | 真实 双精度
            | 整数 整数
            | 日期 UTC时间
            | 数据 字节字符串

这个openP列表命令通过生成PlistBuddy实例来打开plist文件,使用正-反包裹,它为UNIX进程创建一个伪终端。这个发送命令发送一个单数PlistBuddy公司向特定Plist发出命令。最后,命令是中的原语远程monad调用Plist伙伴。重复前面的示例:

解释方式> 第页 <- openP列表 “example.plist”
解释方式> 发送 第页 $  { 添加 [“用户”] (字符串 “弗雷德·弗林斯通”); 添加 [“年龄”] (整数 39) }
解释方式> 发送 第页 $ 得到 []
Dict公司 [(“年龄”,整数 39),(“用户”,字符串 “弗雷德·弗林斯通”)]
解释方式> 发送 第页 $ 得到 [“年龄”]
整数 39
解释方式> 发送 第页 $  { 节约 ; 出口 }

我们现在可以通过编程方式构建PlistBuddy公司操作的一元函数裂缝。此外,因为我们使用发送函数,我们可以操作许多plist同时。

实现PlistBuddy远程Monad

我们有已实现包装中的这个设计警察朋友.实现远程monad的方法有很多种。在这种情况下,PlistBuddy公司是我们的远程monad,由类型的读取器monad构造而成夹板,以及Plist特定异常的异常单子。

新类型 Plist伙伴  = PlistBuddy公司 (例外T PlistError错误 (读卡器T 夹板 IO(输入输出)) )
  衍生 (Functor(仿真器), 适用, 莫纳德, Monad错误 PlistError错误, 单读卡器 夹板, 莫纳迪奥)

新类型 PlistError错误 = PlistError错误 字符串 
 衍生 (显示, 等式)

这个openP列表命令生成/usr/libexec/PlistBuddy shell命令,并返回该命令的句柄过程。

openP列表 :: 文件路径 -> IO(输入输出) 夹板
openP列表 文件名 = 
    (聚四氟乙烯,酸碱度) <- 生成WithPty
                    ...
                    “/usr/libexec/PlistBuddy”
                    [“-x”,文件名]
                    ...
    ...
    返回 $ 夹板 聚四氟乙烯 酸碱度 ...

这个发送命令发送PlistBuddy公司通过执行内部monad连接到plist,使用标准的monad转换器运行函数。略为简化,我们有:

发送 :: 夹板 -> PlistBuddy公司  -> IO(输入输出) 
发送 开发 (PlistBuddy公司 ) = 
    v(v) <- 运行阅读器T (运行ExceptT ) 开发
    案例 v(v) 属于
      左侧 (PlistError错误 消息) -> 失败 消息
       val值 -> 返回 val值

最后,每个PlistBuddy公司命令使用内部实用程序函数调用plist进程命令.举个例子,考虑删除.

命令 :: 夹板 -> 字节字符串 -> IO(输入输出) 字节字符串
命令 分裂 输入 = 
        写入打印 聚四氟乙烯 (输入 <> "\n个")
        recvReply(接收回复) 聚四氟乙烯
    哪里
        聚四氟乙烯 = plist_pty(打印) 分裂

删除 :: [文本] -> PlistBuddy公司 ()
删除 进入 = 
        分裂 <- 
        物件 <- 提升(liftIO) $ 命令 分裂 $ “删除” <>  英国标准.凹面(concat) [ ":" <> 引用文本 e(电子) | e(电子) <- 进入 ]
        案例 物件 属于
          "" -> 返回 ()
          _  -> throwPlist错误 $ PlistError错误 $ “删除失败:” ++ 显示 物件

命令是我们的远程评估器,它使用外部进程shell;发送命令和接收回复。每个远程单体基元,比如删除,使用命令向发送文本命令plist伙伴子进程,并等待回复,在本例中为空字符串。我们将此实现称为弱远程单子,因为每个远程monad原语直接调用远程服务。原语和远程调用,我们不会尝试将命令捆绑到数据包中或进行优化这种交流。总之Plist伙伴远程monad是围绕命令效用函数与外壳对话。设计模式指导了我们的外部API,并帮助了我们构建我们的内部实现。

在下一篇博客文章中,我们将研究一个坚强的远程monad,我们捆绑的地方将多个命令合并到一个数据包中,以分摊使用远程服务器的成本服务。对于任何想了解更多信息的人,我们还有一个关于远程单子,包括列表使用远程monad的现有应用程序,以及指向我们的Haskell研讨会论文。