Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing an F# function

Using F# I've some configuration that I want to serialize to disk using Newtonsoft.Json. The config data is a record type. One of the fields in the record type is a function of type string->bool and is used to compare a string to a given value. e.g.

(fun (x:string) -> x = "1")

Serializing the type succeeds but the field isn't successfully stored (it is recorded as '{}') and therefore deserializing fails. This seems to be by design.

How do I store a function so that it can be used to populate the record type from disk when deserialized and still be executable?

I've looked at quotations as a means of storing expressions as data but I'm not sure if this is the way to go. If it is then I'm struggling to get this working.

How can achieve what I need?

Update;

type Logic = string -> bool

The record type storing config;

type Exclusion = 
| FromDataValue of QItem * Logic * ExcludedItems
| FromDataValuesAnd of (QItem * Logic) seq * ExcludedItems          
| FromDataValuesOr of (QItem * Logic) seq seq * ExcludedItems 

The Exclusion record is populated by a user and runs against some data sets excluding items from the return collection. The QItem represents a string in the dataset that the Logic function is applied to, and if returns true the items in ExcludedItems are excluded from the results.

Initially the config is created by a user within the solution so all works fine. However once a config is created I want to be able to save the config to disc so that it can be loaded and run again when required.

So I need to be able to store this, but I want to be able to store it as a string then run it again when loaded.

like image 413
pilsdumps Avatar asked Mar 05 '23 17:03

pilsdumps


1 Answers

You can serialize a function to JSON and then deserialize it and execute the function later using FSPickler:

open MBrace.FsPickler
open MBrace.FsPickler.Json
open System.IO
open System.Text

let serializer = JsonSerializer(indent = true)
let utf8 = UTF8Encoding(false)

let toJson (x: 'a) =
    use stream = new MemoryStream()
    serializer.Serialize(stream, x)
    stream.ToArray() |> utf8.GetString

let parseJson<'a> json =
    use reader = new StringReader(json)
    serializer.Deserialize<'a>(reader)

let f = fun x -> x + 1

let serialized = toJson f

let deserialized = parseJson<int -> int> serialized

deserialized 1 // returns 2

The serialized JSON for the function looks like this:

{
  "FsPickler": "4.0.0",
  "type": "Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32]",
  "value": {
    "_flags": "subtype",
    "subtype": {
      "Case": "NamedType",
      "Name": "FSI_0002+serialized@23",
      "Assembly": {
        "Name": "FSI-ASSEMBLY",
        "Version": "0.0.0.0",
        "Culture": "neutral",
        "PublicKeyToken": ""
      }
    },
    "instance": {}
  }
}

Although instance is blank, it records the metadata about the anonymous type created for the function. That way, it can invoke the correct code when you call the deserialized version, as long as the function type is available in the AppDomain where you do the deserialization.

EDIT

If you want to literally serialize the logic for the function, you can use FSPickler to serialize a code quotation instead:

open MBrace.FsPickler
open MBrace.FsPickler.Json
open FSharp.Quotations
open FSharp.Quotations.Evaluator
open System.IO
open System.Text

let serializer = JsonSerializer(indent = true)
let utf8 = UTF8Encoding(false)

let toJson (x: 'a) =
    use stream = new MemoryStream()
    serializer.Serialize(stream, x)
    stream.ToArray() |> utf8.GetString

let parseJson<'a> json =
    use reader = new StringReader(json)
    serializer.Deserialize<'a>(reader)

let f = <@ fun x -> x + 1 @>

let serialized = toJson f

let deserialized = parseJson<Expr<int -> int>> serialized

let increment = deserialized |> QuotationEvaluator.Evaluate

increment 1

This way, the quotation gets serialized to JSON with all the logic described as an expression tree, and when you deserialize it you can use the FSharp.Quotations.Evaluator library to turn it into a runnable function that you can invoke.

The JSON is now considerably larger, but this can be deserialized and evaluated anywhere:

{
  "FsPickler": "4.0.0",
  "type": "Microsoft.FSharp.Quotations.FSharpExpr`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32]]",
  "value": {
    "attribs": [
      {
        "attribs": [],
        "term": {
          "Case": "CombTerm",
          "Item1": {
            "Case": "NewTupleOp",
            "Item": {
              "Case": "GenericTypeInstance",
              "GenericDefinition": {
                "Case": "NamedType",
                "Name": "System.Tuple`2",
                "Assembly": {
                  "Name": "mscorlib",
                  "Version": "4.0.0.0",
                  "Culture": "neutral",
                  "PublicKeyToken": "b77a5c561934e089"
                }
              },
              "TypeArgs": [
                {
                  "Case": "NamedType",
                  "Name": "System.String",
                  "Assembly": {
                    "_flags": "cached",
                    "id": 9
                  }
                },
                {
                  "Case": "GenericTypeInstance",
                  "GenericDefinition": {
                    "Case": "NamedType",
                    "Name": "System.Tuple`5",
                    "Assembly": {
                      "_flags": "cached",
                      "id": 9
                    }
                  },
                  "TypeArgs": [
                    {
                      "_flags": "cached",
                      "id": 11
                    },
                    {
                      "Case": "NamedType",
                      "Name": "System.Int32",
                      "Assembly": {
                        "_flags": "cached",
                        "id": 9
                      }
                    },
                    {
                      "_flags": "cached",
                      "id": 15
                    },
                    {
                      "_flags": "cached",
                      "id": 15
                    },
                    {
                      "_flags": "cached",
                      "id": 15
                    }
                  ]
                }
              ]
            }
          },
          "Item2": [
            {
              "attribs": {
                "_flags": "cached",
                "id": 4
              },
              "term": {
                "Case": "CombTerm",
                "Item1": {
                  "Case": "ValueOp",
                  "Item1": {
                    "_flags": "subtype",
                    "subtype": {
                      "_flags": "cached",
                      "id": 11
                    },
                    "instance": "DebugRange"
                  },
                  "Item2": {
                    "_flags": "cached",
                    "id": 11
                  },
                  "Item3": null
                },
                "Item2": {
                  "_flags": "cached",
                  "id": 4
                }
              }
            },
            {
              "attribs": {
                "_flags": "cached",
                "id": 4
              },
              "term": {
                "Case": "CombTerm",
                "Item1": {
                  "Case": "NewTupleOp",
                  "Item": {
                    "_flags": "cached",
                    "id": 12
                  }
                },
                "Item2": [
                  {
                    "attribs": {
                      "_flags": "cached",
                      "id": 4
                    },
                    "term": {
                      "Case": "CombTerm",
                      "Item1": {
                        "Case": "ValueOp",
                        "Item1": {
                          "_flags": "subtype",
                          "subtype": {
                            "_flags": "cached",
                            "id": 11
                          },
                          "instance": "C:\\Users\\aeshbach\\AppData\\Local\\Temp\\~vs220A.fsx"
                        },
                        "Item2": {
                          "_flags": "cached",
                          "id": 11
                        },
                        "Item3": null
                      },
                      "Item2": {
                        "_flags": "cached",
                        "id": 4
                      }
                    }
                  },
                  {
                    "attribs": {
                      "_flags": "cached",
                      "id": 4
                    },
                    "term": {
                      "Case": "CombTerm",
                      "Item1": {
                        "Case": "ValueOp",
                        "Item1": {
                          "_flags": "subtype",
                          "subtype": {
                            "_flags": "cached",
                            "id": 15
                          },
                          "instance": 32
                        },
                        "Item2": {
                          "_flags": "cached",
                          "id": 15
                        },
                        "Item3": null
                      },
                      "Item2": {
                        "_flags": "cached",
                        "id": 4
                      }
                    }
                  },
                  {
                    "attribs": {
                      "_flags": "cached",
                      "id": 4
                    },
                    "term": {
                      "Case": "CombTerm",
                      "Item1": {
                        "Case": "ValueOp",
                        "Item1": {
                          "_flags": "subtype",
                          "subtype": {
                            "_flags": "cached",
                            "id": 15
                          },
                          "instance": 11
                        },
                        "Item2": {
                          "_flags": "cached",
                          "id": 15
                        },
                        "Item3": null
                      },
                      "Item2": {
                        "_flags": "cached",
                        "id": 4
                      }
                    }
                  },
                  {
                    "attribs": {
                      "_flags": "cached",
                      "id": 4
                    },
                    "term": {
                      "Case": "CombTerm",
                      "Item1": {
                        "Case": "ValueOp",
                        "Item1": {
                          "_flags": "subtype",
                          "subtype": {
                            "_flags": "cached",
                            "id": 15
                          },
                          "instance": 32
                        },
                        "Item2": {
                          "_flags": "cached",
                          "id": 15
                        },
                        "Item3": null
                      },
                      "Item2": {
                        "_flags": "cached",
                        "id": 4
                      }
                    }
                  },
                  {
                    "attribs": {
                      "_flags": "cached",
                      "id": 4
                    },
                    "term": {
                      "Case": "CombTerm",
                      "Item1": {
                        "Case": "ValueOp",
                        "Item1": {
                          "_flags": "subtype",
                          "subtype": {
                            "_flags": "cached",
                            "id": 15
                          },
                          "instance": 25
                        },
                        "Item2": {
                          "_flags": "cached",
                          "id": 15
                        },
                        "Item3": null
                      },
                      "Item2": {
                        "_flags": "cached",
                        "id": 4
                      }
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    ],
    "term": {
      "Case": "LambdaTerm",
      "Item1": {
        "isMutable104": false,
        "name": "x",
        "stamp": 0,
        "typ": {
          "_flags": "cached",
          "id": 15
        }
      },
      "Item2": {
        "attribs": {
          "_flags": "cached",
          "id": 4
        },
        "term": {
          "Case": "CombTerm",
          "Item1": {
            "Case": "StaticMethodCallOp",
            "Item": {
              "Case": "GenericMethodInstance",
              "GenericDefinition": {
                "Case": "Method",
                "Signature": "T3 op_Addition[T1,T2,T3](T1,T2)",
                "IsStatic": true,
                "DeclaringType": {
                  "Case": "NamedType",
                  "Name": "Microsoft.FSharp.Core.Operators",
                  "Assembly": {
                    "Name": "FSharp.Core",
                    "Version": "4.4.1.0",
                    "Culture": "neutral",
                    "PublicKeyToken": "b03f5f7f11d50a3a"
                  }
                },
                "ReflectedType": null
              },
              "TypeArgs": [
                {
                  "_flags": "cached",
                  "id": 15
                },
                {
                  "_flags": "cached",
                  "id": 15
                },
                {
                  "_flags": "cached",
                  "id": 15
                }
              ]
            }
          },
          "Item2": [
            {
              "attribs": {
                "_flags": "cached",
                "id": 4
              },
              "term": {
                "Case": "VarTerm",
                "Item": {
                  "_flags": "cached",
                  "id": 40
                }
              }
            },
            {
              "attribs": {
                "_flags": "cached",
                "id": 4
              },
              "term": {
                "Case": "CombTerm",
                "Item1": {
                  "Case": "ValueOp",
                  "Item1": {
                    "_flags": "subtype",
                    "subtype": {
                      "_flags": "cached",
                      "id": 15
                    },
                    "instance": 1
                  },
                  "Item2": {
                    "_flags": "cached",
                    "id": 15
                  },
                  "Item3": null
                },
                "Item2": {
                  "_flags": "cached",
                  "id": 4
                }
              }
            }
          ]
        }
      }
    }
  }
}
like image 192
Aaron M. Eshbach Avatar answered Mar 12 '23 09:03

Aaron M. Eshbach