Wednesday, March 14, 2018

Use of F#



Initial Reaction to F#

Upon reading that F# was an up and coming programming language I decided to try it out.  Even more so upon discovering that the F must be for functional since I thought maybe, as a Microsoft supported product, there would be more examples of how to use it than for Elixir.

It hasn't gone all that well.

I discovered two downloads, one for yet another compiler that has to run from a DOS window and a second that was supposed to be an IDE with a GUI (Integrated Development Environment, Graphical User Interface).

I haven't been able to use the IDE GUI download (Visual Studio Code - Ionide-fsharp).  When I try to debug any application it results in a request for the environment where there are lots of possibilities.  I initially selected C, C#, a couple of others, and one for F# (ionide-fsharp if I remember correctly).  When I attempted to act on an F# file and look through the possible environment selections, the F# one isn't presented.  And when I go back to look through the panel with the list of possibilities, the F# option is no longer presented.  So I have not been able to do anything with it.

The other download (Microsoft VS Code\Code.exe) I have been able to use although it seems to be inconsistent in its ability to compile.  For instance, F# allows a "variable" to actually be a variable by specifying "mutable" when it is declared.  But at times it will report an error that a portion of the object (such as a field of a record structure) cannot be changed; that is, that it isn't mutable.  An example will be provided later.  Another, for instance, is that I was able to move a value into an array of a structure type (again if I remember correctly) but I couldn't extract it again.  That is, after copying the value into the array it was forever lost.  Or so, at least, it seemed.

Another thing that I found strange:  I had the "bright" idea that it might be easier to parse the lines of the Learn to Code GR file by reading one character at a time and immediately checking if the character was one of the ones that delimited the fields of interest.  This worked well enough.  But resulted in finding the strange part.  As I read a single character from the file, it had to be read into a buffer at the same position that it was in the file.  That is, the implementation of the read of N bytes of the file starting at M couldn't be to a buffer that was sized as N bytes.  Instead, to read the entire file this way would mean having a buffer at least the size of the contents of the file.

Besides these problems with the compiler, one of the reasons for trying F# was that I thought there would be plenty of examples of its use from Microsoft so that it would be easier to obtain some answers than for Elixir.  Yet each time I looked up a feature the Microsoft site would have that examples for C, C++, and C# were available but that a F# example wasn't available.

Oh well.  With the ability to use mutable I was making progress with the Learn to Code GR application while having the problems with the compiler's idiosyncrasies.  But, of course, this was unhelpful to being able to use Elixir since all the talk on the internet was to avoid using mutable since it wasn't a functional language feature and how learning to do without it would allow one to find out how wonderful a functional language could be.  Needless to say I'm still awaiting that moment and still in favor of languages that allow me to code.

Another problem was sometimes the compiler would degree a construct like
  let valueArray = [| for i in 0 .. 19 -> "" |]
as unknown when it was referenced later and sometimes it would recognize that it did exist.  It seemed to just depend upon which errors it was reporting.

And yet another problem would be that it would declare perfectly valid constructs in any other language as code to be implicitly ignored.  This is, not allowing certain code due, I suppose, having deduced that the code segment could never execute when, in fact, if it allowed the code sequence, it would execute as conditions varied.

By reworking the code I was able to get around problems such as the above by using do loops instead of while loops.  However, since there isn't a "break" to exit the loop early I had to allow all the loops to continue to the end while adding an if statement to avoid actually doing anything from the point where one would normally exit the loop.  That is, the compiler seemed to have problems with the code inside of a while loop.

Using it reminded me of my experience with Java Script.  With Java Script html files I had to wait until running the application to find that the file had to be fixed.  That is, no compiler.  And with the .js files the compiler missed various erroneous constructs so they had to be found when attempting to run the application.  So I learned that only small changes should be made at a time; the better to be able to find where an error had been introduced.

Likewise with this F# compiler.  A change could cause the compiler to report many errors to lines of code that hadn't been reported to be in error before.  That is, the actual error could be far removed from the ones being reported.  Therefore, like Java Script I found it best to make changes in small increments so if errors were reported elsewhere I would know that the reported code was not in error and that it was something in the newly added/changed code that was causing the problem.

Although I couldn't find examples on Microsoft sites of F# code that did provide examples for other Microsoft supported languages I did find later on http://visualfsharp.com/ which provides information on "Microsoft Visual F# Programming" with text and examples in 30 different sections.  Although its examples aren't real world examples they were helpful.  (By real world I mean that, for instance for a car dealership, the data would be pre-canned in the code rather than coming from a database.  That is, the inventory along with the price, etc would be in the code whereas in the real world a change in such data would not require a change to the program.) 

I read through the entire set of online pages and copied the contents into a Word document so that I could refer to before I returned to coding the Learn to Code application.

Learn to Code GR

My attempt to use F# was to repeat the Learn to Code GR application of all my recent posts.

As mentioned above, I coded it in small segments to avoid the reporting of errors where no error existed.  Likewise, so that errors could be immediately fixed before proceeding.  Note, as a for instance, an error that occurred at the very end.  The code
  . . .
  let mutable maxPair : KeyTable = nullItem
  . . .
        if maxSum > maxPair.Sum then
          maxPair.Key <- item.Key
          maxPair.Value <- item.Value
          maxPair.Sum <- maxSum 
doesn't compile.  Each of the lines to set one of maxPair fields yields the error
error FS0005: This field is not mutable
even thou maxPair has been declared to be mutable.  However, either of the constructs
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }
and
          let tempPair = {
              Key = item.Key
              Value = item.Value
              Sum = maxSum
              }
          maxPair <- tempPair
compile satisfactorily.  That is, the record fields of a mutable record variable can't be assigned individually but can be in total.

Another difference from what occurred using other languages that I learned about was that reading a line from a text file does not result in the trailing carriage return and line feed/new line characters being input.  Therefore, all the lines of the file that are input are like the last line of the file.  That is, without either of the trailing CR or NL characters.  Therefore, the statements in the FindSeparator function that check for CR and NL could be removed.  This does simplify the parse of the line since it makes all the lines have a similar format.

Another comment is that I found using an array of a record type object much easier to use than attempting to use a Key and KeyTable class that I used in other languages that supported a class.  Therefore, the code declares a KeyTable record type and then a keyTable array of null KeyTable objects.  (This because the array has to be sized and initialized when it is declared.)

The declaration of a function and of a variable (where variables are either constant or variable depending upon whether declared as mutable) both start with the let keyword and have the = sign to denote that the body follows.  The specification of the type is usually not necessary since F# is supposed to be able to figure it out.  But I have chosen to supply it (at least most of the time).  '<-' is used to assign a new value to a mutable variable. 

The use of indentation and white space is mostly used rather than brackets, trailing ';' symbols, ',' in function calling parameters to use as separators, and such.  Therefore, careful attention is required in regards to indentation so as to group code as desired. 

The F# version of the Learn To Code GR application follows.

module LearnToCodeGRModule =
  open System
  open System.IO
  open System.Net

  // Create record type
  type KeyTable = {
       Key : string
       Value : string
       Sum : int
       }

  // Continue use of record type by creating global keyTable array
  let nullItem = {
      Key = ""
      Value = ""
      Sum = 0
      }
  let mutable keyTable : KeyTable array =
    [| for i in 0..59 -> nullItem |]


  // Add new record to keyTable
  let AddRecord (start : int) (key : string) (value : string) =

    let mutable srchItem : KeyTable = nullItem
    let mutable i : int = start
    let mutable cont : string = "Yes"
   
    for i = start to keyTable.Length-1 do
      srchItem <- keyTable.[i]
      if srchItem.Sum = 0 then
        if cont = "Yes" then
          let mutable newItem : KeyTable = {
              Key = key
              Value = value
              Sum = 1
              }
          keyTable.[i] <- newItem
          cont <- "No"

//end AddRecord function

  // Update keyTable with new key, value pair
  let Update (key : string) (value : string) =

    let mutable ignoreItem : string = "No"
    let mutable item : KeyTable = nullItem
    for i = 0 to keyTable.Length-1 do
      item <- keyTable.[i]

      if ignoreItem = "No" then
        if item.Sum = 0 then // reached empty location without a match
          AddRecord i key value
          ignoreItem <- "Yes"
        elif item.Key = key then // key matches to array item
          if item.Value = value then // value also matches
            let mutable modItem : KeyTable = {
                Key = item.Key
                Value = item.Value
                Sum = item.Sum + 1
                }
            keyTable.[i] <- modItem
            ignoreItem <- "Yes"

//end of Update function

  // Find next field of interest indicator character
  let HT : char = '\t'
  let CR : char = '\r'
  let NL : char = '\n'
  let FindSeparator(segment : string) : int =
    let mutable sepLocation = -1
    for c = 0 to segment.Length-1 do
      if sepLocation = -1 then
        if segment.[c] = HT || segment.[c] = CR || segment.[c] = NL then
          sepLocation <- c
    if sepLocation = -1 then // no final separator
      sepLocation <- segment.Length
    sepLocation
//end of FindSeparator function

  // Parse the line and Update the keyTable
  let ParseLine(line) =
    let mutable endField = -1
    let mutable partialLine : string = line
    let mutable discard : string = ""
    let mutable key : string = ""
    let mutable value1 : string = ""
    let mutable value2 : string = ""
    for f = 0 to 3 do // four fields of interest expected
      let endField : int = FindSeparator(partialLine)
      let mutable seg : string = partialLine.[0..endField-1]
      if f = 0 then
        discard <- seg
      elif f = 1 then
        key <- seg
      elif f = 2 then
        value1 <- seg
      else
        value2 <- seg
      partialLine <- partialLine.[endField+1..partialLine.Length-1]

    // Update keyTable with the key, value pairs of the line
    Update key value1
    Update key value2

//end of ParseLine function

  // Read and process lines from the file of path
  let ByLine(path) =
    use input = File.OpenText(path)
    printfn "Data read from the file follows"
    while not input.EndOfStream do
      let line = input.ReadLine()
      printfn "%s" line
      ParseLine( line )
//end of ByLine function

  // Read the file and perform the task.  This is the main function.
  ByLine( "C:/Source/LearnToCodeGR/max-col-sum-by-Key.tsv" )

  // Report the results
  // Note: Instead of invoking a Report function at the end of the ByLine
  //       function it is included here as an extension of the main function
  printfn ""
  printfn "Report of the results"
  printfn "Key  Value Sum  Total Max"
  let mutable maxPair : KeyTable = nullItem
  let mutable keyTotal = 0
  let mutable maxSum = keyTable.[0].Sum
  let mutable currentKey = keyTable.[0].Key

  let mutable item : KeyTable = nullItem

  for i = 0 to keyTable.Length-1 do
    item <- keyTable.[i]
    if item.Sum <> 0 then // a non-null array position
      if item.Key <> currentKey then
        printfn "                 %d   %d" keyTotal maxSum
        currentKey <- item.Key // new key
        keyTotal <- item.Sum   // initial Sum of new key
        maxSum <- item.Sum
        if maxSum > maxPair.Sum then
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }
      else
        if item.Sum > maxSum then
          maxSum <- item.Sum
        if maxSum > maxPair.Sum then
          maxPair <- { Key = item.Key
                       Value = item.Value
                       Sum = maxSum }

        keyTotal <- keyTotal + item.Sum
      printfn "%s   %s    %d" item.Key item.Value item.Sum

  printfn "                 %d   %d" keyTotal maxSum // of last element
  printfn " "
  printfn "Key and Value with the most references is"
  printfn "Key  Value Sum"
  printfn "%s   %s    %d" maxPair.Key maxPair.Value maxPair.Sum 

where the results, as seen on the console or to a file piped to capture the console output – that is,
learntocodegr > LearnToCodeGRresults.dat
are as follows.

Data read from the file follows
0,912_NUM   1000  1     1
0,912_NUM   1000  2     1
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   1000  1     1
0,912_NUM   1000  2     2
0,912_NUM   1000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  2     2
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   2000  2     2
0,912_NUM   2000  1     1
0,912_NUM   2000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   3000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  3     3
0,912_NUM   4000  2     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  1     1
0,912_NUM   4000  4     2
0,912_NUM   4000  1     1
0,912_NUM   5000  2     2
0,912_NUM   5000  2     2
0,912_NUM   5000  1     1
0,912_NUM   5000  1     1
0,912_NUM   5000  1     1
0,912_NUM   5000  2     2
0,912_NUM   5000  3     1
0,912_NUM   5000  2     2
0,912_NUM   5000  4     3
0,912_NUM   5000  3     3

Report of the results
Key  Value Sum  Total Max
1000   1    13
1000   2    7
                 20   13
2000   1    16
2000   2    4
                 20   16
3000   1    20
                 20   20
4000   1    15
4000   3    2
4000   2    2
4000   4    1
                 20   15
5000   2    8
5000   1    7
5000   3    4
5000   4    1
                 20   8

Key and Value with the most references is
Key  Value Sum
3000   1    20





No comments: