在音乐的世界里,主流艺术家总是引领着潮流和趋势。如果在last.fm上有个人资料,那么可以编写一个小工具来查询和处理听歌统计数据,以自动化地推荐主流艺术家。本文将介绍如何使用F#编程语言来完成这项任务,并展示F#的一些优势,比如类型提供者和与对象相比,函数单元测试的简便性。
F#是一种多范式编程语言,它支持函数式、命令式、面向对象和逻辑编程范式。它由微软开发,并且是.NET框架的一部分。F#的语法简洁,类型系统强大,非常适合进行数据密集型任务的处理。
任务是获取last.fm排行榜上的前50名艺术家,将他们的名字转换成一个集合,移除已经被推荐过的艺术家,替换艺术家名字中的非URL字符,以便进行后续的API调用。然后,对每个艺术家进行额外的API调用,获取他们的听众数量。将这些信息转换成一个更简洁的数据类型,只包含艺术家的名字和听众数量。最后,根据听众数量对艺术家进行排序,听众数量多的艺术家更主流。
F#的管道操作符(|>)可以非常自然地表达这个工作流程。以下是使用F#实现的代码示例:
let result = getTopArtists
|> getTopArtistNames
|> removeAlreadyRecomendedArtists
|> getUrlEncodedArtistNames
|> mapArtistNamesToArtistInfo getArtistInfo
|> getArtistsShortInfo
|> orderArtistsByListenersCount
类型提供者是F#最引人注目的特性之一。它允许以强类型的方式访问许多上下文,如Web API、数据库模式等,从而在编译时获得支持,并享受IDE自动完成等便利。
在应用中,首先导入FSharp.Data库,然后声明API响应的片段,并使用JsonProvider创建类型。这样就可以在编译时获得强类型响应的支持。
让仔细看看以下函数:
let mapArtistNamesToArtistInfo getArtistInfoFn artists =
artists
|> Array.map (fun i -> getArtistInfoFn i)
getArtistInfoFn负责与远程Web API的交互。以下是如何对这种情况进行单元测试的示例:
let getArtistInfoStub input =
match input with
| "Nokturanl Mortum" -> 1
| "Krobak" -> 2
| _ -> 3
[
这比典型的可测试的面向对象解决方案要优雅得多,后者需要引入接口,将其注入调用者类,并在测试项目中引入一些重量级的模拟库。
细心的读者可能已经注意到,依赖于Web API的无故障运行,这并不是健壮编程的标志。为了正确处理,将采用铁路编程的概念。
主要思想是将函数的成功和失败执行编码到返回类型中,以便管道中的所有函数都使用一些有用的业务逻辑处理成功结果,而不成功的结果将被排除在进一步执行之外。
以下是实现的示例:
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
let switch switchFunction1 switchFunction2 input =
match switchFunction1 input with
| Success s -> switchFunction2 s
| Failure f -> Failure f
let (>=>) switchFunction1 switchFunction2 input =
switch switchFunction1 switchFunction2 input
现在可以将返回值包装在提供的类型中:
let getTopArtists () =
try
let path = String.Format(getTopArtistsPattern, baseUrl, userName, apiKey)
let data = Http.Request(path)
match data.Body with
| Text text -> Success(TopArtists.Parse(text).Topartists.Artist)
| _ -> Failure "getTopArtists. Unexpected format of reponse message"
with
| ex -> Failure ex.Message
F#提供了一个内置的Result类型,允许放弃ROPHelper。管道现在看起来如下:
let pipeline =
getTopArtists()
|> Result.bind getTopArtistNames
|> Result.bind removeAlreadyRecomendedArtists
|> Result.bind getUrlEncodedArtistNames
|> Result.bind (mapArtistNamesToArtistInfo getArtistInfo)
|> Result.bind getArtistsShortInfo
|> Result.bind orderArtistsByListenersCount
注意,必须使getTopArtists()接受unit,以便被Result.bind接受。如下进行模式匹配结果:
[