Function order in F#

Today I learned about the F# and operator. tl;dr: it is used for co-recursivity.

So on internet, someone was looking to flatten nested tuples in F#. Inspired by others Q&A, I answered it. I mixed a bit of reflection in a recursion function to make it work:

let isTuple tuple =
tuple.GetType() |> Reflection.FSharpType.IsTuple

let getFields (tuple: obj) =
tuple |> Reflection.FSharpValue.GetTupleFields |> Array.toList

let rec flatten fields =
fields
|> List.collect(
fun tuple ->
if isTuple tuple
then flatten (getFields tuple)
else [tuple]
)

let namer(tuple: obj) =
if isTuple tuple
then tuple |> getFields |> flatten
else [tuple]

namer (1, "test") |> printfn "%A"
namer (1, ("2", (3, "chicken"))) |> printfn "%A"

I don't remember if it hits me when I wrote it but today it did. We are using namer in flatten's collect. Let's change that.

let rec flatten fields =
fields
|> List.collect(
namer
)

Try it online!

Alright now we face an error FS0039: The value or constructor 'namer' is not defined.. This error is a feature. In programming, it is a good practice to order your functions to be read like a book (i.e from top to bottom). If a function f calls a function g, then g should be declared before f. Most popular languages dont care. Neither JavaScript nor C# at least.

morpheus meme with what if I told you that good practices could be mandatory caption

In F#, this good practice is indeed mandatory.

The reason is that when the compiler sees namer in the body, it doesn’t know about the function because it hasn’t finished compiling it yet! Top to bottom is mandatory. So what are we going to do in our case? We can't put namer above flatten nor we can do the other way around. We want them at the same level. So, how to have two methods calling each other? let rec... and... is the syntax we seek.

let rec f() = 
g()
and g() =
f()

The new version is clearly DRY-er:

let isTuple tuple =
tuple.GetType() |> Reflection.FSharpType.IsTuple

let getFields (tuple: obj) =
tuple |> Reflection.FSharpValue.GetTupleFields |> Array.toList

let rec flatten fields =
List.collect namer fields

and namer (tuple: obj) =
if isTuple tuple
then tuple |> getFields |> flatten
else [tuple]

namer (1, "test") |> printfn "%A"
namer (1, ("2", (3, "chicken"))) |> printfn "%A"

Try it online!

You can learn more about it over here. And this is how I learned about the and operator.



Tags: dotnet, fsharp, til

← Back home