Практика кодирования для F #
Я занимаюсь F # в Visual Studio 2010. Я разработчик с большим опытом разработки кода и архитектуры в объектно-ориентированных языках, таких как С# и 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()
Мои вопросы: что я сделал хорошо в этом сценарии? Какие части можно было бы сделать лучше?
Я действительно с нетерпением жду некоторых идей, и я очень рад тому, что знаком с функциональными парадигмами!:)
Заранее спасибо
Ответы
Ответ 1
В принципе, ваш код в порядке.
Есть только некоторые моменты, которые можно упростить с синтаксической точки зрения.
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 ]
Ответ 2
let createPeople filter = new XElement(XName.Get("People"),
ppl |> List.filter(filter) |> List.map createPerson
)
Эта часть может быть deforested вручную, или вы можете надеяться, что компилятор обезвредит ее вам.
В принципе, существует промежуточная структура (список фильтрованных людей), которая, если она скомпилирована наивно, будет выделена для обслуживания только один раз. Лучше применять createPerson
для каждого элемента, как это принято в случае, если они находятся или выходят, и напрямую строить окончательный результат.
EDIT:
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
не работает, но я не специалист.
Ответ 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
Все из них используют данные с чередованием.
let filter (id, name) =
id % 2 = 1
let allFilter (id, name) =
true
let createPerson (id, name) =
()
Ответ 4
Мне также недавно понадобилось преобразовать 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))