我一直在使用Visual Studio 2010中的F#。我是一名开发人员,在面向对象语言(如C#和Java)方面拥有更多代码/架构设计经验。
扩展我的技能并帮助做出更好的决定我正在尝试不同的语言来做不同的事情。特别是使用函数式语言(在这种情况下为F#)“正确”地编写代码。
一个简单的例子是生成一些XML,然后添加一些过滤器来消除一些元素。
这是我的代码:
open System
open System.Xml.Linq
let ppl:(string * string) list = [
("1", "Jerry");
("2", "Max");
("3", "Andrew");
]
/// Generates a Person XML Element, given a tuple.
let createPerson (id:string, name:string) = new XElement(XName.Get("Person"),
new XAttribute(XName.Get("ID"), id),
new XElement(XName.Get("Name"), name)
)
/// Filter People by having odd ID's
let oddFilter = fun (id:string, name:string) -> (System.Int32.Parse(id) % 2).Equals(1)
/// Open filter which will return all people
let allFilter = fun (id:string, name:string) -> true
/// Generates a People XML Element.
let createPeople filter = new XElement(XName.Get("People"),
ppl |> List.filter(filter) |> List.map createPerson
)
/// First XML Object
let XmlA = createPeople oddFilter
/// Second XML Object
let XmlB = createPeople allFilter
printf "%A\n\n%A" XmlA XmlB
/// Waits for a keypress
let pauseKey = fun () -> System.Console.ReadKey() |> ignore
pauseKey()
我的问题是:在这种情况下我做得怎么样?哪些部分可以做得更好?
我真的很期待一些想法,我也很兴奋熟悉功能范例! :)
提前致谢
答案 0 :(得分:13)
原则上,你的代码没问题。
从语法的角度来看,只有一些点可以简化。
let ppl:(string * string) list = [
("1", "Jerry");
("2", "Max");
("3", "Andrew");
]
编译器能够自己推断出大多数类型:
let ppl = [ "1", "Jerry";
"2", "Max";
"3", "Andrew" ]
当然,由于currying:
,您可以重新编写此类过滤器let oddFilter (id:string, name:string) = (int id) % 2 = 1
let allFilter (id:string, name:string) = true
最大的改进是将索引与名称分开,让程序进行编号。您不必使用字符串而不是数字,并且可以使用更多惯用的无元组函数:
let ppl = [ "Jerry"; "Max"; "Andrew" ]
let oddFilter id name = id % 2 = 1
let allFilter id name = true
let createPerson id name = ...
部分
ppl |> List.filter(filter) |> List.map createPerson
将被重写为
[ for (index, name) in List.mapi (fun i x -> (i, x)) do
if filter index name then
yield createPerson (string index) name ]
答案 1 :(得分:4)
let createPeople filter = new XElement(XName.Get("People"),
ppl |> List.filter(filter) |> List.map createPerson
)
这部分可以手动deforested,或者你可以希望编译器为你砍伐它。
基本上,有一个中间结构(过滤后的人员列表),如果这是天真编译的,将被分配为只服务一次。最好在每个元素上应用createPerson
,因为它们决定是否进入,并直接构建最终结果。
编辑:
cfern贡献了这个被砍伐的createPeople
版本:
let createPeople filter =
new XElement(
XName.Get("People"),
List.foldBack
(fun P acc -> if filter P then (createPerson P)::acc else acc)
ppl
[])
注意:因为在filter
或createPerson
中可能存在副作用,在F#中,编译器很难决定自己进行森林砍伐。在这种情况下,在我看来,砍伐森林是正确的,因为即使filter
有副作用,createPerson
也没有,但我不是专家。
答案 2 :(得分:3)
大部分时间没有特定原因(通常是性能)砍伐森林是一个坏主意。您认为哪一个更易于阅读且不易出错?脱离上下文的森林砍伐只会增加代码的复杂性和/或耦合。
let createPeople filter ppl =
ppl
|> List.mapi (fun i x -> (i, x))
|> List.filter filter
|> List.map createPerson
let createPeople filter ppl =
[ for (index, name) in ppl |> List.mapi (fun i x -> (i, x)) do
if filter (index, name) then
yield createPerson (index, string) ]
let createPeople filter ppl =
(ppl |> List.mapi (fun i x -> (i, x)), [])
||> List.foldBack (fun P acc -> if filter P then (createPerson P)::acc else acc)
一旦习惯了语法功能,你就可以放弃ppl。
let createPeople filter =
List.mapi (fun i x -> (i, x))
>> List.filter filter
>> List.map createPerson
所有这些都使用了tupled数据。
let filter (id, name) =
id % 2 = 1
let allFilter (id, name) =
true
let createPerson (id, name) =
()
答案 3 :(得分:0)
我最近还需要将XSL转换为XML文件。 这是F#我曾经这样做过的。
使用.net方法有一些有趣的怪癖。
(* Transforms an XML document given an XSLT. *)
open System.IO
open System.Text
open System.Xml
open System.Xml.Xsl
let path = @"C:\\XSL\\"
let file_xml = path + "test.xml"
let file_xsl = path + "xml-to-xhtml.xsl"
(* Compile XSL file to allow transforms *)
let compile_xsl (xsl_file:string) = new XslCompiledTransform() |> (fun compiled -> compiled.Load(xsl_file); compiled)
let load_xml (xml_file:string) = new XmlDocument() |> (fun doc -> doc.Load(xml_file); doc)
(* Transform an Xml document given an XSL (compiled *)
let transform (xsl_file:string) (xml_file:string) =
new MemoryStream()
|> (fun mem -> (compile_xsl xsl_file).Transform((load_xml xml_file), new XmlTextWriter(mem, Encoding.UTF8)); mem)
|> (fun mem -> mem.Position <- (int64)0; mem.ToArray())
(* Return an Xml fo document that has been transformed *)
transform file_xsl file_xml
|> (fun bytes -> File.WriteAllBytes(path + "out.html", bytes))