question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

memoize fails if an argument to memoized function is illegal from F# compiler perspective

See original GitHub issue

Description

If a memoized function takes an F# type as an argument, any value passed in must be legal in order for memoization to work. Whilst this may sound desirable, we have a situation where we’re trying to memoize union case values. If a union case value takes fields, the caller is not required to pass them in, so we “fill them in” by creating (and memoizing, but that’s irrelevant) a default value. The default value may not be legal from F#'s perspective because there’s only so much we can fill in. We then use that default value when calling another memoized function, and that’s where the problem arises, because memoizing eventually invokes GetHashCode on the “illegal” object and that crashes with NullReferenceException.

Repro steps

module Repro =
    open FSharpPlus
    open Xunit

    type SomeRecord = {
        Foo: List<string>
    }

    let getStuff (_: obj) : int * string =
        (42, "Answer to everything")

    let getStuffMemoized : (obj -> int * string) = memoizeN getStuff

    [<Fact>]
    let ``repro (crashes with System.NullReferenceException)`` () =
        let defaultRecordValue = {
            Foo = Unchecked.defaultof<List<string>>
        }
        getStuffMemoized defaultRecordValue |> ignore

Expected behavior

Ideally, it would just treat any null values in the object structure as zero when calculating a hash. To be honest, I’m not sure that this is in FSharpPlus’ hands because it’s the GetHashCode generated by the F# compiler that is crashing. It assumes a valid object, presumably for performance reasons.

Actual behavior

System.NullReferenceException : Object reference not set to an instance of an object.
  SomeRecord.GetHashCode(IEqualityComparer comp) line 0
  MemoizationKeyWrapper`1.GetHashCode(IEqualityComparer comp)
  MemoizationKeyWrapper`1.GetHashCode()
  ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
  MemoizeN.getOrAdd[a,b](ConcurrentDictionary`2 cd, FSharpFunc`2 f, a k)
  MemoizeN@28.Invoke(FSharpFunc`2 arg10, a arg20)

Known workarounds

None that will work in a generic fashion (without the caller having to do some custom mangling of their types).

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
kentcbcommented, Oct 7, 2021

Ended up getting around this by defining my own record type with custom equality. The record encapsulates the two fields I was originally memoizing on, but overrides GetHashCode + Equals so that only the relevant field is used. By memoizing a function taking this custom record as a parameter, it memoizes how I want.

0reactions
gustycommented, Oct 7, 2021

For the record, here is the original suggestion to fix this https://github.com/fsharp/fslang-suggestions/issues/92

Read more comments on GitHub >

github_iconTop Results From Across the Web

Facing issues writing a JavaScript memoization function
Your memoize function isn't returning a function. function memoize(fn) { var cache = {}; return function() { if (cache[arguments[0]]!
Read more >
Memoize - Make functions faster by trading space for time
This function takes no arguments, and as far as Memoize is concerned, it always returns the same result. Memoize is wrong, of course,...
Read more >
Laugh at a silly incorrect memoize function (then tell me ...
This function takes a pure function f as an argument and returns a function that behaves almost exactly like f , except that...
Read more >
Memoization: What, Why, and How | Kyle Shevlin
Memoization is a technique that enhances a function by creating and using a cache to store and retrieve results of that function. Memoization...
Read more >
Memoization
A function can only be memoized if it is referentially transparent; that is, only if calling the function has exactly the same effect...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found