Should We Say That F# Employs Good IO Methods?

I have done a lot of DevOps research in the past, and I agree with F#'s programming system a lot. My intuition is that there is nowhere else to go after F# in terms of refined functional coding. I’ve noticed a few straining points, however, in relation to IO methods. F# appears to be preeminent when it comes to “piping” forms eg. |> and → kind of relationships, but it would appear that conversion methods get in the way a lot, especially, if your going to use decimals instead of whole integer numeric values. Working with decimals is, evidently, a setback in all coding systems. Smooth coding should involve plenty of nonconformity methods. Here I managed to get over working with decimals when doing mathematic functions, but sending first results to be implemented again doesn’t look so convenient. The following example merely regards one numeric value to be read into another subroutine. What functional, active, or reusable coding element should I use?

#Part A

let Min = 0.74625

let A = [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]

A |> List.iteri (fun i x → if x = Min then

let B = sprintf “%d” (i+1)

printf “%s” B) // what does F# normally use to “pipe forward” results in “pipline fun” things like these so that a print command doesn’t have to be used in a first instance like this, but only in the final resolve?

let C = B // C can not be anything but a “pure string” or else will not read
printf “%s” C

#Part B

Lets say variable A as demonstrated in Part A had been completely immutable and did not contain any static list of numeric values, objects, or elements for structered data. F# maintains a few higher order system functions that make it irrelevant to do indexing of a numeric set of data to perform additional maths. List. functionalities have diligently shown themselves to work more systematically and technically accurate than any other eg. Seq.

let List = “114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;”

let A = [List] // what F# principles will accurately take a simple string like the one demonstrated in List as a variable defining a set of values and “build” or “format” the initial orientation of the data so that a higher order List.avg function can be used on data such as in the defined List variable above or any hailding reorderded data such as from other related F# modules?

Are you looking for something like this?

> #r "nuget: FSharpx.Extras";;
> open System;;
> open FSharpx;;
> "114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;" 
- |> String.splitString [|";"|] StringSplitOptions.RemoveEmptyEntries |> Array.map float

val it: float[] =
  [|114.546; 1.3665; 0.74625; 83.926; 13.977; 1.10852; 1.18148; 133.098|]

FSharp as a coding system community does a loud call for functional or reusable code. It looks like you got decimals to read into an Array.map using FSharpx and an operator, float. I resorted - |> to the more apparent |> and ran the script using the printf “%f” command, but something’s not reading. What am I missing?

yeah the - is just how FSI renders multiple lines, so - |> is indeed supposed to just be |>.
I can run that exact code in my FSI window and things work


(my font renders |> as a triangle, but it’s the same as a normal |>)
Can you share your code and what error you’re getting?

Thanks. After moving a few things around I got your code to work exactly the way you coded it using FSI.

Your part A appears to be looking for a specific value in the list and printing the 1-based indexes of matches in the list. You asked how to save the printing until the end. You can achieve this using 2 List functions: indexed and choose. Then print the found indexes at the end.

let chooser target (index, value) =
    if value = target then
        Some (index + 1)
    else
        None
let target = 0.74625
let matchingIndexes =
    [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]
    |> List.indexed
    |> List.choose (chooser target)
printfn "%A" matchingIndexes
// [3]

I believe the question about converting to decimal is covered by another response. There is a .NET built-in function for string splits but it is not as idiomatic to F# as the FSharpx function.

RE: Using pipes. You can definitely overdo pipeline operators. They are great working with data. But can become very problematic when you try to use them to encode workflows with branching outcomes. When in doubt, use let to name the value between steps or match for branching. Then adjust for the desired level of conciseness vs readability.

RE: List vs Seq. Seq allows you to build a chain of computations. These computations are not evaluated until the code tries to read values from the seq. Whereas List and Array functions are executed immediately and return a new List or Array in each step (unless otherwise noted in the function, like Array.set). So in the code above, the items are processed twice – once for indexed and another time for choose. Whereas a Seq version would only iterate the original list once, but with extra memory/call overhead for the computation chain. Overhead that is not worth it for small lists. Seq is useful when data processing steps are dynamically generated. Seq can work with arbitrarily large lists (e.g. reading from a large file) or even infinite lists without consuming all memory. Though in most cases you could load/process batches of N items using a list/array to similar effect. For small amounts of data and predefined process steps, using Seq is typically less efficient than List/Array.

If you have more complicated processing that would require too many List/Array steps (each one creating a new List/Array) but also don’t justify the overhead of Seq, then you would typically use fold or a recursive loop if you need early exit. Either way lets you plug in your custom processing behavior. When I can’t imagine the series of built-in functions to accomplish what I want, I typically use a recursive loop, then refactor to a fold if I don’t need early exit. And sometimes I later figure out built-in functions to accomplish it.

Yes, I prefer FSharp over other coding systems because “readability” was one of its #1 rules. I see how your answer is the right answer. Thank you. Regarding my initial post and F# system I was shooting for readability and higher order. My sense is that both idiomatic and higher order functionality is at work in your example. Formulaically, how come FSharp has a problem building, piping, or converting printed results according to the, technically, string method (or any of its the array, seq, list, map alternates)?

  • eg. in Part A. “B” must conveniently be redefined by the variable “C” and the both ‘high and readable’ method at work must continuum

I’m not quite sure what you mean or perhaps what you are comparing F# against. What would be your ideal way to print the results?

In your original code, B and C seem unnecessary. printf/printfn support the same formatting codes as sprintf. So instead of this:

let b = sprintf "%d" (i + 1)
printf "%s" b

You could do this:

printf "%d" (i + 1)
// or use %A to print any type
printf "%A" (i + 1)
printf "%A" [1;2;3]
// or use string interpolation syntax
printf $"1-based index = {i + 1}"
// prints: 1-based index = 3

If you wanted to compose logging into the code, you can pipe that as well.

let tee f v =
    f v; v

let log name value =
    printfn $"{name} = {value}"

let oneBasedMatch target (index, value) =
    if value = target then
        Some (index + 1)
    else
        None
        
let target = 0.74625
let chooser = oneBasedMatch target
let matchingIndexes =
    [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]
    |> List.indexed
    |> List.choose chooser
    |> tee (log "found indexes")
// prints: found indexes = [3]
// matchingIndexes = [3]

let matchingIndexes2 =
    [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]
    |> List.indexed
    |> List.choose (chooser >> tee (Option.iter (log "found at")))
// prints: found at = 3
// matchingIndexes2 = [3]

matchingIndexes = matchingIndexes2
// = true

I might use something like this for data exploration but not for app code. In app code I want to avoid introducing side effects where it can be pure logic. And instead use automated tests to validate expected results. I typically only use logging within apps in side effect or infrastructure code (e.g. DB operations, request logs, service start/end).

Exactly. So, in Part A. “B” becomes a defined proponent eg. B or let B = sprintf “%d” (i + 1). There are other possibilities to represent Part A.'s results as you’ve discussed. Turns out, however, that although neither B containing immutable results or simply (i +1) does not interpolate so that either one of these representations of the same results can be redefined by another variable according to common coding practices eg. “let C = B” or more straining, simply “let C = (i+1).” Either way it would appear repeating this step outside of the List.iteri’s function related closures is eminently not an option. Is there a workaround for this? In other words, C must simply be able to reprint results B outside of the List.iteri function’s closures.

You can capture B’s value to an outside mutable variable C like this.

let list = [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]
let target = 0.74625
let mutable c = String.Empty
list
|> List.iteri (fun i item ->
    if item = target then
        let b = $"{i + 1}"
        c <- b
        // or just: c <- $"{i + 1}"
    else () // this line optional for unit-returning if expression
)

printfn "%s" c
// 3

Or you can do the same side effect inside choose if you want to capture C while building the list of the matched indexes. This isn’t “functional” in that it uses a side effect. But a mutable variable can be useful for performance reasons. And if the side effect is local to this function (i.e. just mutating local variable C, returning C’s final value and the list, but not calling print here), it will still be deterministic (aka “pure” / referentially transparent / “functional”) to callers.

Is that what you were looking for?

And by the way you could also use a fold to stay purely functional while keeping C and building the output list. I have to run right now but I can give a quick example later. I often end up using fold when I need to build/track multiple things at once from a list.

Here’s a version using fold and no mutation.

let list = [114.546;1.3665;0.74625;83.926;13.977;1.10852;1.18148;133.098;]
let target = 0.74625

type State = {
    C: string option
    FoundRev: int list // Rev indicating reversed order
}

let update state (i, value) =
    if value = target then
        let foundI = i + 1
        { C = Some $"{foundI}"
          FoundRev = foundI :: state.FoundRev }
    else
        state

let initState = { C = None; FoundRev = [] }
let state =
    list
    |> List.indexed
    |> List.fold update initState

printfn $"{state}"
// { C = Some "3"; FoundRev = [3] }

I like to call the folder function update because it is updating the state from each item in the list.

How does an expression like $“{i + 1}” function variable to a regular printf command? After compiling your example it seemed to be missing points designated outside of the List.iteri function closures. Could you rewrite your example with fitting dummy data so that I know that it actually works?

$"{i + 1}" evaluates to a string and would be comparable to sprintf "%i" (i + 1). printf differs from both sprintf and string interpolation with $"..." in that it doesn’t it doesn’t evaluate to anything, but instead prints a value to the console. So you wouldn’t say let b = printf "%i" (i + 1). If you wanted to both bind a variable to the value and print it to the console, you could do that with two lines, e.g.,

list
|> List.iteri (fun i item ->
    if item = target then
        let b = $"{i + 1}" 
        printf "%s" b
        c <- b
)

or use the tee function that @kspeakman defined in his earlier example

let tee f v = 
  f v; v

list
|> List.iteri (fun i item ->
    if item = target then
        let b = $"{i + 1}" |> tee (printf "%s")
        c <- b
)

Once you have @kspeakman’s example compiling, it’s easy to plug in your own data by adding in your own let list = ... and let target = ... bindings above it. I’m able to verify that c has the correct value using the input values from your original post, though the example doesn’t print anything out. You’d need to add printf in there if you wanted something printed.

@ntwilson explains this difference. This article has in depth explanation on sprintf vs printf and also exposure to the built-in .NET analogs String.Format and Console.Write. The article does not mention the string interpolation syntax like $"{i + 1}". This feature was added just last year. Interpolation syntax generally compiles to an sprintf call.

Note that if you need to convert to strings in a high-performance way, you typically do not want to use these methods. Many StackOverflow questions falsely conclude poor performance of other code because of sprintf in benchmarks. What to use in performance scenarios? For simple conversions, you can use the string conversion function. E.g. let b = string (i + 1). For building a string from multiple pieces, you typically want to use StringBuilder. Though it is less convenient than the other mentioned ways of formatting strings. Some reference articles here and here. These mention C#'s string interpolation as a comparison point, but note that F#'s string interpolation and sprintf are generally slower than C#'s equivalents due to extra convenience functionality.

Sure. I updated the examples to define list and target.

Is there an explicit reason you’re using FSI and not https://dotnetfiddle.net/? I find it kind of monotonous to have to resort to FSI for compiling? Using data like let list = [1;2;3] and for target let item = 2 in dotnetfiddle.net and the briefer example that disregards so called “tee function” do you notice any functional results?

I prefer FSI because I can run it locally, I can write my code in my IDE of choice, and I can reference other compiled code if I’m working in a larger project. And I can control the compiler version - I see that dotnetfiddle is using a version of F# that’s a bit old and doesn’t support string interpolation (the $"{i + 1}" syntax). That’s just explaining my personal preference though - if you don’t need the latest F# features, and dotnetfiddle is meeting your needs, no need to get used to FSI if it feels monotonous to you.

Anyway, yes using dotnetfiddle or FSI I can get results if I plug in let list = [1;2;3] and let target = 2:

(Note that using dotnetfiddle I had to replace $"{i + 1}" with sprintf "%i" (i + 1)).

Are you experiencing some trouble running the code to see the results? If you’re getting any kind of error, we can probably help if you share the code and the text of the error.

That doesn’t surprise me. It seems to be very worthy for diagnosing a host of potentials, but I’ve noticed a slim few advanced system open includes that do not work there eg. FSharp.Data, FSharpx, and any media related and WPF related stuff will not work with it. Otherwise, it’s been very healthy to test my starting scripts there many a time.

So, I certainly noted that this code works. Your details helped to identify the quirks. Shortening sprintf to $ seems to make a lot of sense. I get the sense that its frequently used.

I’ve been using an FSX (F# script) file rather than working in FSI directly. The file can be run in a terminal with dotnet fsi filename.fsx. I’ve been using VS Code + Ionide to play with code for this post. It provides code analysis and a run button. And things like sending selected lines to FSI still works as well for easy experimentation.

To use nuget packages like FSharpx, FSharp.Data in FSI and FSX files, you have to use the special load directive before you can open the namespace.

#r "nuget: FSharpx"
open FSharpx

I discovered VS Code is a heavy IDE application for the laptop that I’m using. I know it can be used cross-platform, but does VS Code have “auto-run” capabilities?