j0z
j0z

Reputation: 35

Function argument is null, even though a non-null argument is passed

F# newbie here, and sorry for the bad title, I'm not sure how else to describe it.
Very strange problem I'm having. Here's the relevant code snippet:

let calcRelTime (item :(string * string * string)) =
     tSnd item
     |>DateTime.Parse
     |> fun x -> DateTime.Now - x
     |> fun y -> (floor y.TotalMinutes).ToString()
     |>makeTriple (tFst item) (tTrd item) //makeTriple switches y & z. How do I avoid having to do that? 


let rec getRelativeTime f (l :(string * string * string) list) = 
    match l with
    | [] -> f
    | x :: xs -> getRelativeTime (List.append [calcRelTime x] f) xs

I step through it with Visual Studio and it clearly shows that x in getRelativeTime is a 3-tuple with a well-formed datetime string. But when I step to calcRelTime item is null. Everything ends up returning a 3-tuple that has the original datetime string, instead of one with the total minutes past. There's no other errors anywhere, until the that datetime string hits a function that expects it to be an integer string.

Any help would be appreciated! (along with any other F# style tips/suggestions for these functions).

Upvotes: 0

Views: 271

Answers (2)

Fyodor Soikin
Fyodor Soikin

Reputation: 80714

item is null, because it hasn't been constructed yet out of its parts. The F# compiler compiles tupled parameters as separate actual (IL-level) parameters rather than one parameter of type Tuple<...>. If you look at your compiled code in ILSpy, you will see this signature (using C# syntax):

public static Tuple<string, string, string> calcRelTime(string item_0, string item_1, string item_2)

This is done for several reasons, including interoperability with other CLR languages as well as efficiency.

To be sure, the tuple itself is then constructed from these arguments (unless you have optimization turned on), but not right away. If you make one step (hit F11), item will obtain a proper non-null value.

You can also see these compiler-generated parameters if you go to Debug -> Windows -> Locals in Visual Studio.

As for why it's returning the original list instead of modified one, I can't really say: on my setup, everything works as expected:

> getRelativeTime [] [("x","05/01/2015","y")]
val it : (string * string * string) list = [("x", "y", "17305")]

Perhaps if you share your test code, I would be able to tell more.

And finally, what you're doing can be done a lot simpler: you don't need to write a recursive loop yourself, it's already done for you in the many functions in the List module, and you don't need to accept a tuple and then deconstruct it using tFst, tSnd, and tTrd, the compiler can do it for you:

let getRelativeTime lst = 
   let calcRelTime (x, time, y) =
      let parsed = DateTime.Parse time
      let since = DateTime.Now - parsed
      let asStr = (floor since.TotalMinutes).ToString()
      (x, asStr, y)
   List.map calRelTime lst

Upvotes: 1

Huusom
Huusom

Reputation: 5912

let getRelativeTime' list = 
    let calc (a, b, c) = (a, c, (floor (DateTime.Now - (DateTime.Parse b)).TotalMinutes).ToString())
    list |> List.map calc

Signature of the function is val getRelativeTime : list:('a * string * 'b) list -> ('a * 'b * string) list

You can deconstruct item in the function declaration to (a, b, c), then you don't have to use the functions tFst, tSnd and tTrd.

The List module has a function map that applies a function to each element in a list and returns a new list with the mapped values.

Upvotes: 1

Related Questions