HiveBrain v1.2.0
Get Started
← Back to all entries
snippetMinor

Kata: Natural Sort

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
sortkatanatural

Problem

I am choosing to learn F# for my own enjoyment. I am getting to the point where concepts of F# seem to be pretty easy, but understanding some of the whys and whens is a bit harder.

Before I get into the code and the explanation, let me put my question up front. I am asking where can I find better advice on formatting F# code for readability? Or can someone give me a few guiding tips based off of the example given below?

So what are the best practices for code format and file layout?

Now to the explanation of the code.

I have started practicing coding Kata's in F# just to allow me to flex the language a little. The following program is an implementation of a natural sort. This was my first attempt to solving the problem in a TDD fashion in F#, as such I chose to forgo any framework as I did not want to deal with figuring out how to use any of them and not break the functional paradigm.

So the code below carries a light weight unit test framework.

Here is the code for the natural sort:

```
namespace Katas
open System.Linq

module NaturalSortKata =
exception InvalidException of string

type Comparison =
| Equal
| Lesser
| Greater
static member Compare x y =
if x = y then
Equal
elif x > y then
Greater
else
Lesser

type ChunckType =
| NumberType
| StringType
| Unknown
static member GetType (c : char) =
if System.Char.IsDigit(c) then
NumberType
else
StringType

member this.Compare other =
match other with
| ty when ty = this -> Equal
| Unknown -> Lesser
| NumberType when this = Unknown -> Greater
| NumberType -> Lesser
| StringType -> Greater

let natualCompare (left : s

Solution

Your code formatting is very readable. The minor issue is that you don't have to indent after namespace declaration. I would choose to open module directly to save a level of indentation (see attached code later).


So what are the best practices for code format and file layout?

I think F# code formatting guidelines have very concrete suggestions on these issues.

There are a few small problems with your code:

-
In naturalCompare, you should move local functions to the top so the logic of the function is clear.

-
In compare, if you remove when in the last clause, you don't have to superficially throw an exception.

-
Value tests is more readable in the form of normal list declaration.

let tests = [ test01; test02; test03; test04; test05; test06; test07; test08; test09; ]


Here is reformatted version of Katas.NaturalSortKata module. You could do the same for Katas.Testing.Tests.

// 0) Declare module to save a level of indentation
module Katas.NaturalSortKata

open System.Linq

type Comparison =
| Equal
| Lesser
| Greater
    static member Compare x y =
        if x = y then
            Equal
        elif x > y then
            Greater
        else
            Lesser

type ChunckType =
| NumberType
| StringType
| Unknown
    static member GetType (c : char) =
        if System.Char.IsDigit(c) then
            NumberType
        else
            StringType

    member this.Compare other = 
        match other with
        | ty when ty = this -> Equal
        | Unknown -> Lesser
        | NumberType when this = Unknown -> Greater
        | NumberType -> Lesser
        | StringType -> Greater

let naturalCompare (left : string) (right : string) = 
    // 1) Move local functions on top
    let fix str =
        new System.String( str |> List.rev |> List.toArray )

    let gatherChunck str = 
        let rec gather str acc =
            match str with
            | [] ->
                let (ty, l) = acc
                (ty, fix(l))
            | fistLetter::rest ->
                match acc with
                | (ty, _) when ty = Unknown ->
                    let t = ChunckType.GetType(fistLetter)
                    gather rest (t, fistLetter :: [])
                | (ty, l) when ty = ChunckType.GetType(fistLetter) ->
                    gather rest (ty, fistLetter::l)
                | (ty, l) -> (ty, fix(l))

        gather str (Unknown, [])

    let rec compare (left : string) (right : string) =
        if (not (left.Any())) || (not (right.Any())) then
            match left.Length, right.Length with
            | llen, rlen when llen = rlen -> Equal
            | llen, rlen when llen > rlen -> Greater
            | llen, rlen -> Lesser // 2) Remove superficial when guard
        else
            let lt, lChunk = left |> Seq.toList |> gatherChunck 
            let rt, rChunk = right |> Seq.toList |> gatherChunck

            match lt.Compare rt with
            | Equal ->
                if lChunk = rChunk then
                    let lVal = left.Replace(lChunk, "")
                    let rVal = right.Replace(rChunk, "")

                    compare lVal rVal
                else
                    match lt with
                    | NumberType ->
                        Comparison.Compare (System.Int64.Parse(lChunk)) (System.Int64.Parse(rChunk))
                    | _ ->
                        Comparison.Compare lChunk rChunk
            | _ ->
                lt.Compare(rt)

    if left = right then
        Equal
    else
        compare left right


UPDATE:

After answering your question, I started developing a source code formatter for F#. More information can be found on Gihub.

Results from the tool or its companion formatting guideline can give you some hints for good formatting.

Code Snippets

let tests = [ test01; test02; test03; test04; test05; test06; test07; test08; test09; ]
// 0) Declare module to save a level of indentation
module Katas.NaturalSortKata

open System.Linq

type Comparison =
| Equal
| Lesser
| Greater
    static member Compare x y =
        if x = y then
            Equal
        elif x > y then
            Greater
        else
            Lesser

type ChunckType =
| NumberType
| StringType
| Unknown
    static member GetType (c : char) =
        if System.Char.IsDigit(c) then
            NumberType
        else
            StringType

    member this.Compare other = 
        match other with
        | ty when ty = this -> Equal
        | Unknown -> Lesser
        | NumberType when this = Unknown -> Greater
        | NumberType -> Lesser
        | StringType -> Greater

let naturalCompare (left : string) (right : string) = 
    // 1) Move local functions on top
    let fix str =
        new System.String( str |> List.rev |> List.toArray )

    let gatherChunck str = 
        let rec gather str acc =
            match str with
            | [] ->
                let (ty, l) = acc
                (ty, fix(l))
            | fistLetter::rest ->
                match acc with
                | (ty, _) when ty = Unknown ->
                    let t = ChunckType.GetType(fistLetter)
                    gather rest (t, fistLetter :: [])
                | (ty, l) when ty = ChunckType.GetType(fistLetter) ->
                    gather rest (ty, fistLetter::l)
                | (ty, l) -> (ty, fix(l))

        gather str (Unknown, [])

    let rec compare (left : string) (right : string) =
        if (not (left.Any())) || (not (right.Any())) then
            match left.Length, right.Length with
            | llen, rlen when llen = rlen -> Equal
            | llen, rlen when llen > rlen -> Greater
            | llen, rlen -> Lesser // 2) Remove superficial when guard
        else
            let lt, lChunk = left |> Seq.toList |> gatherChunck 
            let rt, rChunk = right |> Seq.toList |> gatherChunck

            match lt.Compare rt with
            | Equal ->
                if lChunk = rChunk then
                    let lVal = left.Replace(lChunk, "")
                    let rVal = right.Replace(rChunk, "")

                    compare lVal rVal
                else
                    match lt with
                    | NumberType ->
                        Comparison.Compare (System.Int64.Parse(lChunk)) (System.Int64.Parse(rChunk))
                    | _ ->
                        Comparison.Compare lChunk rChunk
            | _ ->
                lt.Compare(rt)

    if left = right then
        Equal
    else
        compare left right

Context

StackExchange Code Review Q#20955, answer score: 6

Revisions (0)

No revisions yet.