Yesterday I released “FAKE – F# Make” version 1.33.0. This release has lots of small bug fixes and a couple of new features.
Important links:
Git helpers -Fake.Git.dll
Git is a distributed revision control system with an emphasis on speed. Git was initially designed and developed by Linus Torvalds for Linux kernel development. Every Git working directory is a full-fledged repository with complete history and full revision tracking capabilities, not dependent on network access or a central server.
[Wikipedia]
In the last couple of months I worked on small helper library for controlling Git. This library is now released as part of “FAKE – F# Make”. You can find the source code at http://github.com/forki/FAKE/tree/master/src/app/Fake.Git/.
Features:
- Repository handling
- Submodules
- init, clone, information about submodules
- Branches
- checkout, create, delete, merge, rebase, tag, pull, push, reset, commit, …
- SHA1 calculation
Documentation generation with James Gregory’s docu tool
What’s a docu? A documentation generator for .Net that isn’t complicated, awkward, or difficult to use. Given an assembly and the XML that’s generated by Visual Studio, docu can produce an entire website of documentation with a single command.
[docu website]
Fake comes bundled with James Gregory’s documentations generator “docu”, which converts XML-Documentation comments into HTML files. All you need to do is downloading a template and calling the docu task inside your build script:
!+ (buildDir + "*.dll")
   |> Scan
    |> Docu (fun p ->
        {p with
            ToolPath = docuPath + "docu.exe"
            TemplatesPath = templatesSrcDir
            OutputPath = docsDir })
Since FAKE builds its own documentation with docu I started to add more (and hopefully better) XML doc comments. My plan is to describe more and more of the internal FAKE functions in the next releases. An updated HTML-document (generated via a docu task) can be found at http://www.navision-blog.de/fake/.
Side by side specification
For Test-driven development (TDD) it’s sometimes nice to have the specifications next to the implementation files since the specs are considered as documentation.
By using a tool like VSCommands it is possible to group the implementation with the specs (see also http://gist.github.com/457248).
This “side by side specification” makes TDD a lot easier but of course we don’t want to deploy the specification classes and the test data.
|
==> |
|
FAKE has a new feature which automatically removes all specification files and test framework references according to a given convention:
Target "BuildApp" (fun _ –>
     !+ @"src\app\**\*.csproj"
        |> Scan
        |> Seq.map (
            RemoveTestsFromProject
                AllNUnitReferences     // a default references convention
                AllSpecAndTestDataFiles // a default file convention
                )
        |> MSBuildRelease buildDir "Build"
        |> Log "AppBuild-Output: "
)
The conventions are simple functions and can be customized e.g.:
/// All Spec.cs or Spec.fs files and all files containing TestData
let AllSpecAndTestDataFiles elementName (s:string) =
    s.EndsWith "Specs.cs" ||
      s.EndsWith "Specs.fs" ||
      (elementName = "Content" && s.Contains "TestData")
Miscellaneous
- SQL Server helpers are moved to Fake.SQL.dll
- Additional functions for attaching and detaching databases.
- FileHelper.CopyCached function was added
- Copies the files from a cache folder. If the files are not cached or the original files have a different write time the cache will be refreshed.
- EnvironmentHelper.environVarOrDefault added
- Retrieves the environment variable or a given default.
- Fixed Issue 3: toRelativePath calculates paths with ..\..\ if needed
- Added a build time report to the build output.
- XPathReplace and XMLPoke tasks added.
- Replaces text in an XML file at the location specified by an XPath expression.
- ILMerge task added
- Windows Installer XML (WiX) task added
Tags:
F#,
F-sharp Make,
Fake,
git,
SideBySideSpecification,
SQL Server,
TDD,
xml,
XPath
Today I’m starting a new blog post series about solving code katas in F# and with the help of my NaturalSpec project. A code kata is a programming exercise which helps to improve your skills through practice and repetition. In this series we want to use the Test Driven Development TDD approach which means in the context of NaturalSpec that we have to write our specs before we implement the algorithm.
Problem Description
“The game of yahtzee is a simple dice game. Each round, each player rolls five six sided dice. The player may choose to reroll some or all of the dice up to three times (including the original roll). The player then places the roll at a category, such as ones, twos, sixes, pair, two pairs etc. If the roll is compatible with the score, the player gets a score for this roll according to the rules. If the roll is not compatible, the player gets a score of zero for this roll.
The kata consists of creating the rules to score a roll in any of a predefined category. Given a roll and a category, the final solution should output the score for this roll placed in this category.”
[codingdojo.org]
Category 1 – Ones, Twos, Threes, Fours, Fives, Sixes
“Ones, Twos, Threes, Fours, Fives, Sixes: The player scores the sum of the dice that reads one, two, three, four, five or six, respectively. For example, 1, 1, 2, 4, 4 placed on "fours" gives 8 points.”
After reading this category description we could come up with the following spec:
module Yahtzee.Specs
open NaturalSpec
let placed_on category list =
printMethod category
calcValue category list
[<Scenario>]
let “Given 1,1,2,4,4 placed on "fours" gives 8 points.“ () =
Given (1, 1, 2, 4, 4)
|> When (placed_on Fours)
|> It should equal 8
|> Verify
Since we really want to implement six different categories we should also add some more scenarios like this one:
[<Scenario>]
let “Given 1,1,6,4,6 placed on "sixes" gives 12 points.“ ()=
Given (1, 1, 6, 4, 6)
|> When (placed_on Sixes)
|> It should equal 12
|> Verify
// …
Now we have some specs but they will all fail since we don’t have anything implemented yet. So we have to come up with a model of dice rolls and categories. As the specs suggests we model the dice roll as a tuple of ints and the category as a discriminated union:
module Yahtzee.Model
type Roll = int * int * int * int * int
type Category =
| Ones
| Twos
| Threes
| Fours
| Fives
| Sixes
The tuple is a natural choice for the dice roll, but for easier calculation we add a helper function which converts it into a list. This allows use to use the standard list functions and therefore summing the values becomes trivial:
let toList (roll:Roll) =
let a,b,c,d,e = roll
[a;b;c;d;e]
let sumNumber number =
Seq.filter ((=) number)
>> Seq.sum
let calcValue category roll =
let list = toList roll
match category with
| Ones -> sumNumber 1 list
| Twos -> sumNumber 2 list
| Threes -> sumNumber 3 list
| Fours -> sumNumber 4 list
| Fives -> sumNumber 5 list
| Sixes -> sumNumber 6 list
Now we can run our scenarios with any NUnit runner. I’m using the default NUnit GUI runner here, which gives me a picture like this:
Category 2 – Pair
“Pair: The player scores the sum of the two highest matching dice. For example, 3, 3, 3, 4, 4 placed on "pair" gives 8.”
The kata description gives us some new scenarios. As seen above we should specify them before writing the code.
[<Scenario>]
let “Given 3,3,3,4,4 placed on "pair" gives 8.“ () =
Given (3, 3, 3, 4, 4)
|> When (placed_on Pair)
|> It should equal 8
|> Verify
[<Scenario>]
let “Given 5,3,5,4,4 placed on "pair" gives 10.“ () =
Given (5, 3, 5, 4, 4)
|> When (placed_on Pair)
|> It should equal 10
|> Verify
[<Scenario>]
let “Given 1,2,3,4,5 placed on "pair" gives 0.“ () =
Given (1, 2, 3, 4, 5)
|> When (placed_on Pair)
|> It should equal 0
|> Verify
Since we use a new category we now have to extend our model and the calcValue function:
type Category =
| Ones
// …
| Sixes
| Pair
// …
let sumAsPair list number =
let numberCount =
list
|> Seq.filter ((=) number)
|> Seq.length
if numberCount >= 2 then 2 * number else 0
let calcValue category roll =
let list = toList roll
match category with
| Ones -> sumNumber 1 list
// …
| Sixes -> sumNumber 6 list
| Pair ->
[1..6]
|> Seq.map (sumAsPair list)
|> Seq.max
Category 3 – Two pairs
“Two pairs: If there are two pairs of dice with the same number, the player scores the sum of these dice. If not, the player scores 0. For example, 1, 1, 2, 3, 3 placed on "two pairs" gives 8.”
[<Scenario>]
let “Given 1,1,2,3,3 placed on "two pair" gives 8.“ () =
Given (1, 1, 2, 3, 3)
|> When (placed_on TwoPair)
|> It should equal 8
|> Verify
[<Scenario>]
let “Given 1,6,6,3,3 placed on "two pair" gives 18.“ () =
Given (1, 6, 6, 3, 3)
|> When (placed_on TwoPair)
|> It should equal 18
|> Verify
[<Scenario>]
let “Given 1,1,2,4,3 placed on "two pair" gives 0.“ () =
Given (1, 1, 2, 4, 3)
|> When (placed_on TwoPair)
|> It should equal 0
|> Verify
Implementing this category is a little bit tricky but with the help of our Pair function and some more standard sequence combinators we can get our spec green:
type Category =
| Ones
// …
| TwoPair
let allPairs =
[for i in 1..6 do
for j in 1..6 -> i,j]
let calcValue category roll =
// …
| TwoPair ->
allPairs
|> Seq.filter (fun (a,b) -> a <> b)
|> Seq.map (fun (a,b) ->
let a’ = sumAsPair list a
let b’ = sumAsPair list b
if a’ = 0 || b’ = 0 then 0 else a’ + b’)
|> Seq.max
Category 4 – Three of a kind
“Three of a kind: If there are three dice with the same number, the player scores the sum of these dice. Otherwise, the player scores 0. For example, 3, 3, 3, 4, 5 places on "three of a kind" gives 9.”
[<Scenario>]
let “Given 3,3,3,4,5 placed on "three of a kind" gives 9“()=
Given (3, 3, 3, 4, 5)
|> When (placed_on ThreeOfAKind)
|> It should equal 9
|> Verify
[<Scenario>]
let “Given 3,4,3,4,5 placed on "three of a kind" gives 0“()=
Given (3, 4, 3, 4, 5)
|> When (placed_on ThreeOfAKind)
|> It should equal 0
|> Verify
Now it is time to refactor our code. The sumAsPair function should be extended to a sumAsTuple function:
type Category =
| Ones
// …
| ThreeOfAKind
let sumAsTuple value list number =
let numberCount =
list
|> Seq.filter ((=) number)
|> Seq.length
let takeBestTuple value list =
[1..6]
|> Seq.map (sumAsTuple value list)
|> Seq.max
if numberCount >= value then value * number else 0
let calcValue category roll =
// …
| Pair -> takeBestTuple 2 list
| TwoPair ->
allPairs
|> Seq.filter (fun (a,b) -> a <> b)
|> Seq.map (fun (a,b) ->
let a’ = sumAsTuple 2 list a
let b’ = sumAsTuple 2 list b
if a’ = 0 || b’ = 0 then 0 else a’ + b’)
|> Seq.max
| ThreeOfAKind –> takeBestTuple 3 list
Category 5 – Four of a kind
“Four of a kind: If there are four dice with the same number, the player scores the sum of these dice. Otherwise, the player scores 0. For example, 2, 2, 2, 2, 5 places on "four of a kind" gives 8.”
[<Scenario>]
let “Given 2,2,2,2,5 placed on "four of a kind" gives 8“ ()=
Given (2, 2, 2, 2, 5)
|> When (placed_on FourOfAKind)
|> It should equal 8
|> Verify
[<Scenario>]
let “Given 2,6,2,2,5 placed on "four of a kind" gives 0“ ()=
Given (2, 6, 2, 2, 5)
|> When (placed_on FourOfAKind)
|> It should equal 0
|> Verify
With the help of the takeBestTuple function this becomes trivial:
type Category =
| Ones
// …
| FourOfAKind
let calcValue category roll =
// …
| FourOfAKind -> takeBestTuple 4 list
Category 6 – Small straight
“Small straight: If the dice read 1,2,3,4,5, the player scores 15 (the sum of all the dice), otherwise 0.”
[<Scenario>]
let “Given 1,2,3,4,5 placed on "Small Straight" gives 15“()=
Given (1,2,3,4,5)
|> When (placed_on SmallStraight)
|> It should equal 15
|> Verify
[<Scenario>]
let “Given 1,2,5,4,3 placed on "Small Straight" gives 15“()=
Given (1,2,5,4,3)
|> When (placed_on SmallStraight)
|> It should equal 15
|> Verify
[<Scenario>]
let “Given 1,2,6,4,3 placed on "Small Straight" gives 0“()=
Given (1,2,6,4,3)
|> When (placed_on SmallStraight)
|> It should equal 0
|> Verify
As in all the above scenarios we don’t assume any specific order in our rolls but for this category it is easier to test if the data is sorted:
type Category =
| Ones
// …
| SmallStraight
let calcValue category roll =
// …
| SmallStraight ->
match list |> List.sort with
| [1;2;3;4;5] -> 15
| _ -> 0
Category 7 – Large straight
“Large straight: If the dice read 2,3,4,5,6, the player scores 20 (the sum of all the dice), otherwise 0.”
[<Scenario>]
let “Given 2,3,4,5,6 placed on "Large Straight" gives 20“()=
Given (2,3,4,5,6)
|> When (placed_on LargeStraight)
|> It should equal 20
|> Verify
[<Scenario>]
let “Given 6,2,5,4,3 placed on "Large Straight" gives 20“()=
Given (6,2,5,4,3)
|> When (placed_on LargeStraight)
|> It should equal 20
|> Verify
[<Scenario>]
let “Given 1,2,6,4,3 placed on "Large Straight" gives 0“()=
Given (1,2,6,4,3)
|> When (placed_on LargeStraight)
|> It should equal 0
|> Verify
Of course the implementation is exactly the same as for the small straight:
type Category =
| Ones
// …
| LargeStraight
let calcValue category roll =
// …
| LargeStraight ->
match list |> List.sort with
| [2;3;4;5;6] -> 20
| _ -> 0
Category 8 – Full house
“Full house: If the dice are two of a kind and three of a kind, the player scores the sum of all the dice. For example, 1,1,2,2,2 placed on "full house" gives 8. 4,4,4,4,4 is not "full house".”
[<Scenario>]
let “Given 1,1,2,2,2 placed on "full house" gives 8.“ () =
Given (1,1,2,2,2)
|> When (placed_on FullHouse)
|> It should equal 8
|> Verify
[<Scenario>]
let “Given 4,4,4,4,4 placed on "full house" gives 0.“ () =
Given (4,4,4,4,4)
|> When (placed_on FullHouse)
|> It should equal 0
|> Verify
[<Scenario>]
let “Given 1,1,2,3,2 placed on "full house" gives 0.“ () =
Given (1,1,2,3,2)
|> When (placed_on FullHouse)
|> It should equal 0
|> Verify
Implementing the FullHouse category is easy if we reuse our solutions to the Two pairs category:
type Category =
| Ones
// …
| FullHouse
let takeBestCombo value1 value2 list =
allPairs
|> Seq.filter (fun (a,b) -> a <> b)
|> Seq.map (fun (a,b) ->
let a’ = sumAsTuple value1 list a
let b’ = sumAsTuple value2 list b
if a’ = 0 || b’ = 0 then 0 else a’ + b’)
|> Seq.max
let calcValue category roll =
// …
| TwoPair -> takeBestCombo 2 2 list
// …
| FullHouse -> takeBestCombo 2 3 list
Category 9 – Yahtzee
“Yahtzee: If all dice are the have the same number, the player scores 50 points, otherwise 0.”
Here we can use NaturalSpec’s ScenarioTemplates in order to specify all Yahtzees:
[<ScenarioTemplate(1)>]
[<ScenarioTemplate(2)>]
[<ScenarioTemplate(3)>]
[<ScenarioTemplate(4)>]
[<ScenarioTemplate(5)>]
[<ScenarioTemplate(6)>]
let “Given n,n,n,n,n placed on "Yahtzee" gives 50.“ n =
Given (n,n,n,n,n)
|> When (placed_on Yahtzee)
|> It should equal 50
|> Verify
[<Scenario>]
let “Given 1,1,1,2,1 placed on "Yahtzee" gives 50.“ () =
Given (1,1,1,2,1)
|> When (placed_on Yahtzee)
|> It should equal 0
|> Verify
The implementation is pretty easy:
type Category =
| Ones
// …
| Yahtzee
let calcValue category roll =
// …
| Yahtzee ->
let a,b,c,d,e = roll
if a = b && a = c && a = d && a = e then 50 else 0
Category 10 – Chance
“Chance: The player gets the sum of all dice, no matter what they read.”
[<Scenario>]
let “Given 1,1,1,2,1 placed on "Chance" gives 6.“ () =
Given (1,1,1,2,1)
|> When (placed_on Chance)
|> It should equal 6
|> Verify
[<Scenario>]
let “Given 1,6,1,2,1 placed on "Chance" gives 11.“ () =
Given (1,6,1,2,1)
|> When (placed_on Chance)
|> It should equal 11
|> Verify
This seems to be the easiest category as we only have to sum the values:
type Category =
| Ones
// …
| Chance
let calcValue category roll =
// …
| Chance -> List.sum list
Conclusion
We used a lot of F#’s sequence combinators, pattern matching and discriminated unions in this kata. I think this shows that F# is very well suited for such a problem and with NaturalSpec we can easily use a TDD/BDD approach.
The complete source code can be found in the NaturalSpec repository.
If you want to know more about a specific part of the kata or NaturalSpec feel free to contact me.
Tags:
BDD,
Code Quality,
F#,
Kata,
KataYahtzee,
NaturalSpec,
nunit,
TDD
Last week I released version 0.29 of my build automation tool “FAKE – F# Make”. The new version comes along with a couple of changes which I will now describe.
F# February 2010 CTP and .NET 4.0 RC
“FAKE – F# Make” should be completely compatible with both, the F# February 2010 CTP and the F# version which is included in Visual Studio 2010 RC.
FAKE self build and binaries on teamcity.codebetter.com
Since “FAKE – F# Make” is a build automation tool, it was always used for it’s own build process. Now this build process could be moved to an open CI server at teamcity.codebetter.com. If you login as a guest you can download the latest “FAKE – F# Make” binaries from there.
FAKE on build servers without F#
With the new F# CTP there is no longer a need for installing F# on the build agents. In fact FAKE itself was built on build agents which don’t have a F# installation.
If you want to create such build scripts you have to do the following:
- Download the standalone zip file of the F# CTP
- Put the bin subfolder into your project tree
- Modify the FSIPath value in the FAKE.exe.config file to match your FSharp bin folder
- If you copy the contents the F# bin folder into ./tools/FSharp/ it should just work.
If you have F# projects you also need to modify your .fsproj files like this:
<PropertyGroup>
….
<FscToolPath>..\..\..\tools\FSharp\</FscToolPath>
</PropertyGroup>
…
<Import Project="..\..\..\tools\FSharp\Microsoft.FSharp.Targets" />
This modifications should take care that MSBuild will use the F# compiler from your tools paths.
Generate your documentations with “docu”
“What’s a docu? A documentation generator for .Net that isn’t complicated, awkward, or difficult to use. Given an assembly and the XML that’s generated by Visual Studio, docu can produce an entire website of documentation with a single command.” [docu Homepage]
“FAKE – F# Make” 0.29 is bundled with this new documentation tool which can easily convert your xml-Documentation into some nice html pages. You can use the tool with the new Docu task like this:
Target? GenerateDocumentation <-
fun _ ->
Docu (fun p ->
{p with
ToolPath = @".\tools\FAKE\docu.exe"
TemplatesPath = @".\tools\FAKE\templates"
OutputPath = docDir })
(buildDir + "MyAssembly.dll")
You will also need the docu templates, which you can download from the product homepage. I’m planning to bundle some basic templates with the next version of FAKE.
What’s next?
At the moment I’m working on ILMerge task for FAKE. I hope to release this with the next version. There are also some open issues with the Mono support but since teamcity.codebetter.com is getting a mono build agent I hope to make some progress here too.
If you have any questions or ideas for new features please contact me.
Tags:
Continuous Integration,
F#,
F-sharp Make
The new version 0.27 of “FAKE – F# Make” comes with new syntactic sugar for build targets and build dependencies. Don’t be afraid the old version is still supported – all scripts should still work with the new version.
The problem
Consider the following target definition:
let buildDir = "./build/"
Target "Clean" (fun _ ->
CleanDir buildDir
)
Target "Default" (fun _ ->
trace "Hello World from FAKE"
)
"Default" <== ["Clean"]
run "Default"
As you can see we are having a lot of “magic strings” for the target names and the dependency definitions. This was always a small shortcoming in FAKE, since this doesn’t allow refactoring and may result in runtime errors.
One of my goals for “FAKE – F# Make” is to remove these strings in future versions. Unfortunately this is not that easy, because it causes a lot of internal issues. In particular logging to the build server is much harder if you don’t have a target name.
The first step
As posted in a bitbucket comment by cipher we could use the “dynamic lookup operator” to remove some of the magic strings without breaking any internal code.
As a result we can rewrite the above sample as:
let buildDir = "./build/"
Target? Clean <-
fun _ -> CleanDir buildDir
Target? Default <-
fun _ -> trace "Hello World from FAKE"
For? Default <- Dependency? Clean
Run? Default
All magic strings suddenly disappeared. I think this syntax looks really nice, but unfortunately the strings are not really gone, since the token Default is only checked at runtime.
The idea for future versions
Since the new syntax is really just syntactic sugar I’m always interested in a better solution. Currently I’m working on a syntax using monads. The result could look like this:
let buildDir = "./build/"
let Clean = target { CleanDir buildDir }
let Default =
target {
trace "Hello World from FAKE"
trace "Another line"
}
Default <== [Clean]
This way the magic string are really gone, but my current problem is retrieving the target name from the let-binding name. Please leave a comment if you have an idea to solve this issue.
Tags:
F#,
F-sharp Make,
Fake