设为首页 收藏本站
查看: 727|回复: 0

[经验分享] 五十种语言的“圣诞快乐”(下):F#实现

[复制链接]

尚未签到

发表于 2017-3-1 10:41:38 | 显示全部楼层 |阅读模式
  不知道大家的圣诞节过的如何?有没有玩点啥有趣的东西?上次的文章中我们主要分析了使用Google Translate进行文字翻译的方式,并使用C#写了一个简单的的翻译程序,效果良好。不过,在平时开发过程中,对于此类问题我常用F#来解决这样的问题。那么使用F#来实现此类任务有什么优势吗?不错,我们现在便来看看这个问题。
  简单的F#实现
  话说F#和.NET框架可以无缝集成,因此理论上之前我们的C#代码怎么写,便可以使用F#来照着抄一遍。例如,我们先定义一个Sync模块,其中先定义一个获取全部目标语言的getLanguages函数:
#light
module Sync
open System
open System.Text.RegularExpressions
open System.Net
open System.Web
open System.Web.Script.Serialization
open System.Collections.Generic
open System.Text
type private LangMap = Dictionary<string, string>
let private getLanguages() =
let url =
&quot;http://translate.googleapis.com&quot; +
&quot;/translate_a/l?client=te&hl=zh-CN&cb=_callbacks_._0g3mb650r&quot;
let webClient = new WebClient()
let script = webClient.DownloadString(url)
let json = Regex.Match(script, @&quot;'tl':({.+})}\)&quot;).Groups.Item(1).Value
let serializer = new JavaScriptSerializer()
serializer.Deserialize<LangMap>(json)
  这个函数与之前C#版本的GetLanguages方法可谓一模一样,其返回结果便是一个字典,存储了目标语言的代号和名称——当然,这边我为Dictionary<string, string>取了一个别名LangMap,这样使用起来感觉更为方便,语义也更加清晰。
  同样的,我们“翻译”一遍之前的Translate方法,变成如今的translateText函数:

let private translateText (text:string) sl tl =
let url =
&quot;https://translate.googleapis.com&quot; +
&quot;/translate_a/t?client=te&format=html&v=1.0&quot;
let encoded = text |> HttpUtility.UrlEncode
let data = sprintf &quot;q=%s&sl=%s&tl=%s&tc=1&quot; encoded sl tl
let webClient = new WebClient()
webClient.Encoding <- Encoding.UTF8
let userAgent = &quot;Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0;)&quot;
webClient.Headers.Add(HttpRequestHeader.UserAgent, userAgent)
let json = webClient.UploadString(url, data)
let serializer = new JavaScriptSerializer()
serializer.Deserialize<string>(json)
  而最终进行多种语言翻译的,则是使用公有的translate函数:

let translate text sl =
let languages = getLanguages()
let translated =
languages
|> Seq.map (fun p -> translateText text sl p.Key)
let names = languages  |> Seq.map (fun p -> p.Value)
Seq.zip names translated |> Seq.toArray
  首先,所有语言会存入一个叫做languages的容器中,它是一个字典,实现了IEnumerable<KeyValuePair<string, string>>,在F#中便是seq<KeyValuePair<string, string>>,因此我们使用Seq模块的map方法将其转化为翻译后的结果,并存放在translated中。然后,我们获得每种语言的名称,最终与翻译的结果zip即可。translate函数最终返回的是一个存放着元组的数组,元组的类型是string * string。
  至于调用,自然是很容易的。F#控制台程序也需要定义main函数,如下:

[<EntryPoint>]
let main args =
let output =
Sync.translate &quot;圣诞快乐&quot; &quot;zh-CN&quot;
|> Array.map (fun (lang, text) -> sprintf &quot;%s:o%s&quot; lang text)
File.WriteAllLines(&quot;output.txt&quot;, output)
Console.ReadLine() |> ignore
0

  当然,如果我们只是把F#用成这样,那实在就没有多大价值了。

异步编程
  刚才的代码放在名为Sync的模块中,很明显这是“同步”的。对于此类IO操作,使用同步IO是一件非常不明智的事情。当然,对于目前这样的小程序来说,使用同步IO并没有什么问题。但是您一会儿也会发现,异步IO对目前的情形也是十分有价值的。
  异步IO除了“不阻塞”的含义在里面之外,我认为还需要包括“事件触发”这个含义在里面。例如,JDK很早就支持了异步IO,但是直到目前为止(JDK 6),它在Windows平台上的相关实现,依旧只是简单的“非阻塞”,而非基于Windows的异步IO机制IOCP。于此相对,它在Linux平台上的实现却使用了高效的epoll。因此,如Mina,Jetty等高性能IO通信框架在Windows下的表现要比Linux差很多——您会经常看到时不时有人以此来证明Windows性能低下,但我倒认为这只能证明Java的跨平台只是“功能”而不是“性能”,意义大打折扣。
  至于跨平台的IO类库并非没有,例如著名的libevent便是一例。不过在JDK 7中“据说”会修复这方面问题,拭目以待。
  对于在Windows平台下真正的异步IO机制IOCP,我之前在多篇文章中有所提及(包括与其有关的IO线程池)。在.NET平台上,各种类库的异步通信方式自然使用了IOCP,自不必提。但是,异步编程的性能虽然好,但它的最大问题还是“方便性”,最基础的一点,由于任务流需要分两部分进行,那么我们自然需要保留上下文吧?即便是如C#中提供了匿名函数,可以自动生成闭包来保持上下文,但是还是有其他问题——例如,异常处理怎么办?
  当然,利用C#中的yield关键字可以简化这些问题,但是F#给我们更好的解决方案。F#应对异步编程提供了一个名为“异步工作流”的结构,它基于F#的“工作流”特性开发了一套异步类库,关于这一点在上次的Comet原型中也有提及。异步工作流的优势在于把一个“二段式”的异步调用合并在一起,这样在调用的时候便可以同步操作的方式进行。如此,无论是逻辑中的上下文信息还是异常处理都变得异常简单。
  对于异步操作的“合并”,例如可以根据最常见的APM模式(即Begin/End)生成“单步”的工作流。不过对于WebClient来说,它的异步操作并非APM形式,而是基于“事件”。不过F#对此也提供了支持,因此我们可以为WebClient扩展两个函数:

type WebClient with
member c.GetStringAsync(url) =
async {
c.DownloadStringAsync(new Uri(url))
let! args = c.DownloadStringCompleted |> Async.AwaitEvent
return args.Result
}
member c.PostStringAsync(url, data) =
async {
c.UploadStringAsync(new Uri(url), data)
let! args = c.UploadStringCompleted |> Async.AwaitEvent
return args.Result
}
  我们在F#中可以“打开”任意一个类型,为其添加新的成员——不过自然这只是如C#中扩展方法那样的语法糖而已。我们为WebClient添加了GetStringAsync与PostStringAsync两个方法,它们返回的都是结果为一个字符串的异步数据流(即Async<string>),执行这个异步数据流之后便可以得到最终的结果。值得注意的是,async块只是在构造一个逻辑块,并没有真正调用。
  于是,我们的getLanguages函数也可进行改写:

let private getLanguages() =
async {
let url =
&quot;http://translate.googleapis.com&quot; +
&quot;/translate_a/l?client=te&hl=zh-CN&cb=_callbacks_._0g3mb650r&quot;
let webClient = new WebClient()
// let script = webClient.DownloadString(url)
let! script = webClient.GetStringAsync(url)
let json = Regex.Match(script, @&quot;'tl':({.+})}\)&quot;).Groups.Item(1).Value
let serializer = new JavaScriptSerializer()
// serializer.Deserialize<LangMap>(json)
return serializer.Deserialize<LangMap>(json)
}
  您是否发现这段代码与之前的变化?没错,两者几乎一模一样。除了它变成了async构造块之外,唯一的区别只是被注释的两行代码。第一行代码中,原本我们使用的是同步的DownloadString函数,而现在我们则通过执行一个异步数据流(使用let!)来得到结果script——当然,执行的形式却是同步的。除此之外,最后返回结果需要使用return来标明。let!和return(以及其他的如return!)等操作符,都是F#编译器对“工作流”构造块(如async)使用中的“强制措施”,以此避免开发人员的误操作。
  同样,translateText函数也将构造一个异步工作流:

let private translateText (text:string) sl tl =
async {
let url =
&quot;https://translate.googleapis.com&quot; +
&quot;/translate_a/t?client=te&format=html&v=1.0&quot;
let encoded = text |> HttpUtility.UrlEncode
let data = sprintf &quot;q=%s&sl=%s&tl=%s&tc=1&quot; encoded sl tl
let webClient = new WebClient()
webClient.Encoding <- Encoding.UTF8
let userAgent = &quot;Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0;)&quot;
webClient.Headers.Add(HttpRequestHeader.UserAgent, userAgent)
let! json = webClient.PostStringAsync(url, data) // changed
let serializer = new JavaScriptSerializer()
return serializer.Deserialize<string>(json) // changed
}
  使用异步工作流的另一个好处在于,我们可以轻易地将异步工作流并行地执行。例如在目前的场景中,我们可以同时发起50多个异步IO请求,但同时却不占用任何线程——它们可能都在等着IO设备(即网卡)发送设备信号呢。于是乎,我们的translate函数便可以这样写:

let translate text sl =
async {
let! languages = getLanguages()
let! translated =
languages
|> Seq.map (fun p -> translateText text sl p.Key)
|> Async.Parallel
let names = languages |> Seq.map (fun p -> p.Value)
return Seq.zip names translated |> Seq.toArray
}
  并这样调用:

let output =
Parallel.translate &quot;圣诞快乐&quot; &quot;zh-CN&quot;
|> Async.RunSynchronously
|> Array.map (fun (lang, text) -> sprintf &quot;%s:%s&quot; lang text)
  这句代码中的Parallel便是指我们刚才开发的模块。这段代码的执行速度会比Sync模块要快很多,因此此时50多个翻译的请求并不是依次发出,而是同时发出,并通过响应IO设备的事件来工作。不阻塞,高效。

异步工作流的同步执行
  之前的代码中,我们是将languages集合转化为一组translateText所生成的异步任务,然后由Async.Parallel合并成一个并行的异步任务。这种调用方式很简单,反而是如果您要将这组异步任务按照顺序一一执行起来比较麻烦。当然,这只是麻烦,并不困难,而且其实同样是非常自然的:

let translateSeq text sl =
let rec translateSeq' targets acc =
async {
match targets with
| [] -> return acc |> List.rev
| t :: ts ->
let! result = translateText text sl t
return! translateSeq' ts (t :: acc)
}
async {
let! languages = getLanguages()
let targets = languages |> Seq.map (fun p -> p.Key) |> Seq.toList
let! translated = translateSeq' targets List.empty
let names = languages |> Seq.map (fun p -> p.Value)
return Seq.zip names translated |> Seq.toArray
}
  您能解释一下这段代码的写法吗?
  最后还是来看看执行效果吧。圣诞已过,新年将至。那么在这里就来祝各位兄弟们新年快乐吧!


  • 阿尔巴尼亚语:Gëzuar vitin e ri
  • 阿拉伯语:كل عام وأنتم بخير
  • 爱尔兰语:Sona
  • 爱沙尼亚语:Head uut aastat
  • 白俄罗斯语:З новым годам
  • 保加利亚语:Честита нова година
  • 冰岛语:Gleðilegt nýtt ár
  • 波兰语:Szczęśliwego nowego roku
  • 波斯语:سال نو مبارک
  • 布尔文(南非荷兰语):Gelukkige nuwe jaar
  • 丹麦语:Godt nytår
  • 德语:Frohes neues Jahr
  • 俄语:С новым годом
  • 法语:Bonne année
  • 菲律宾语:Manigong bagong taon
  • 芬兰语:Hyvää uuttavuotta
  • 韩语:새해 복 많이 받으세요
  • 荷兰语:Gelukkig nieuwjaar
  • 加利西亚语:Feliz ano novo
  • 加泰罗尼亚语:Feliç any nou
  • 捷克语:Šťastný nový rok
  • 克罗地亚语:Sretna nova godina
  • 拉脱维亚语:Laimīgu Jauno gadu
  • 立陶宛语:Laimingų Naujųjų metų
  • 罗马尼亚语:An nou fericit
  • 马耳他语:Is-sena t-tajba
  • 马来语:Selamat tahun baru
  • 马其顿语:Среќна нова година
  • 挪威语:Godt nytt år
  • 葡萄牙语:Feliz ano novo
  • 日语:あけましておめでとう
  • 瑞典语:Gott nytt år
  • 塞尔维亚语:Срећна нова година
  • 斯洛伐克语:Šťastný nový rok
  • 斯洛文尼亚语:Srečno novo leto
  • 斯瓦希里语:Furahia mwaka mpya
  • 泰语:สวัสดีปีใหม่
  • 土耳其语:Yeni yılın kutlu olsun
  • 威尔士语:Happy flwyddyn newydd
  • 乌克兰语:З новим роком
  • 西班牙语:Feliz año nuevo
  • 希伯来语:שנה טובה
  • 希腊语:Ευτυχισμένο το νέο έτος
  • 匈牙利语:Boldog újévet
  • 意大利语:Buon anno
  • 意第绪语:גליקלעכן נייעם יאָר
  • 印地语:नया साल मुबारक हो
  • 印尼语:Selamat tahun baru
  • 英语:Happy new year
  • 越南语:Chúc mừng năm mới
  • 中文(繁体):新年快樂
  • 中文(简体):新年快乐
  所有代码

相关文章


  • 五十种语言的“圣诞快乐”(上):分析与实现
  • 五十种语言的“圣诞快乐”(下):F#实现

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-348720-1-1.html 上篇帖子: Web 发展的十年 下篇帖子: JAVA 及 Eclipse 历史简介...[转帖]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表