Crafting Code in F#
Howard Lewis wrote a nice post "Crafting Code in Clojure" about craftable code. He started with a rather ugly Java class and reached a lovely small Clojure solution. I was curious how it would be looked written in F#. Have a look at the result:
(the Fact attribute is from xUnit.net library, and =? operator is from wonderful Unquote one).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let sortedKeyList = | |
Seq.map (Map.toSeq >> Seq.map fst) | |
>> Seq.concat | |
>> Seq.distinct | |
>> Seq.sort | |
>> String.concat ", " | |
>> (function "" -> "<none>" | s -> s) | |
[<Fact>] | |
let ``Sorted key list``() = | |
[[], [], "<none>" | |
["fred", true], ["barney", true; "wilma", true], "barney, fred, wilma" | |
["fred", true], [], "fred" | |
["fred", true], ["fred", true; "barney", true], "barney, fred"] | |
|> List.iter (fun (m1, m2, res) -> sortedKeyList [Map.ofSeq m1; Map.ofSeq m2] =? res) |
It's odd that F# core library does not contain Map.keys/Map.values functions. Otherwise, the code would be a bit more shorter.
However, maps aren't used in static languages like F# to represent domain types. Hence, the more idiomatic version:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Person = { Name: string; Flag: bool } | |
let sortedKeyList = | |
List.concat | |
>> List.map (fun { Name = name } -> name) | |
>> Seq.distinct | |
>> Seq.sort | |
>> String.concat ", " | |
>> (function "" -> "<none>" | s -> s) | |
[<Fact>] | |
let ``Sorted key list``() = | |
let persons = List.map (fun name -> { Name = name; Flag = true }) | |
[ | |
[], [], "<none>" | |
["fred"], ["barney"; "wilma"], "barney, fred, wilma" | |
["fred"], [], "fred" | |
["fred"], ["fred"; "barney"], "barney, fred" | |
] | |
|> List.iter (fun (a, b, res) -> sortedKeyList [a |> persons; b |> persons] =? res) |
Update 27.02.2018: Scala added
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
case class Person(name: String, flag: Boolean) | |
def sortedKeyList(xs: List[List[Person]]) = { | |
xs.flatten | |
.map(_.name) | |
.distinct | |
.sortBy(identity) | |
.mkString(", ") match { | |
case "" => "<none>" | |
case s => s | |
} | |
} | |
class Tests extends FunSuite with Matchers { | |
test("sorted key list") { | |
def people(xs: List[String]) = xs.map(Person(_, flag = true)) | |
val data = List( | |
(Nil, Nil, "<none>"), | |
(List("fred"), List("barney", "wilma"), "barney, fred, wilma"), | |
(List("fred"), Nil, "fred"), | |
(List("fred"), List("fred", "barney"), "barney, fred")) | |
for ((a, b, res) <- data) | |
sortedKeyList(List(people(a), people(b))) should equal(res) | |
} | |
} |
Very simple, very readable.
Comments