Fungible


Getting started with Fungible

Let's start with the core functionality by loading the basic references and opening the appropriate namespaces. Barb is an optional dependency, but we'll be using it in later examples.

1: 
2: 
3: 
4: 
5: 
6: 
#r "Fungible.dll"
#r "Barb.dll"
#r "Fungible.Barb.dll"

open Fungible
open Fungible.Core

Next we need to define a record tree structure to operate on.

1: 
2: 
3: 
4: 
5: 
6: 
type Date = { Day: byte; Month: byte; Year: uint16 }
type Location = { Address: string option; City: string option; 
                  State: string option; Country: string option }
type Person = { Names: string []; DOBs: Date []; 
                Citizenships: string []; Locations: Location []; 
                Fields: Map<string, string> }

As well as a sample to play around with. In this case we are only using one record sample for clarity, but Fungible was desinged to be used with record counts in the hundreds of thousands to millions.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let testPersons =
    [
        { Names = [| "Rick Minerich" |]; DOBs = [| { Day = 8uy; Month = 24uy; Year = 1886us } |]
          Citizenships = [| "Mars"; "Twitter" |]
          Locations = [| { Address = None; City = Some "New York"; State = Some "NY"; Country = None } |]
          Fields = Map.empty
        }
    ] 

For a simple starting example we will just transform the City field within the Locations member of person to be uppercase. When the type is None no action will be taken, when it's Some the transform will be applied.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
let upperFunMapExpr = 
    let upperFun (s: string) = s.ToUpper()
    <@@ upperFun @@> |> Map

let ex1 = 
    let functionMap = [(["City"; "Locations"], [ upperFunMapExpr ])] |> Map.ofList
    let transform = genrateRecordTransformFunction<Person> (FungibleCoreSettings.Default) functionMap
    testPersons |> List.map transform

let ex2 = 
    let functionMap = [(["Address"; "Locations"], [ upperFunMapExpr ])] |> Map.ofList
    let transform = genrateRecordTransformFunction<Person> (FungibleCoreSettings.Default) functionMap
    testPersons |> List.map transform

But what if you want to uppercase many different fields? Just add a path for each place you want to change with the corresponding function.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let ex3 =
    let functionMap = [ ["Names"], [upperFunMapExpr] 
                        ["Citizenships"], [upperFunMapExpr] 
                        ["Address"; "Locations"], [upperFunMapExpr] 
                        ["City"; "Locations"], [upperFunMapExpr] 
                        ["State"; "Locations"], [upperFunMapExpr] 
                        ["Country"; "Locations"], [upperFunMapExpr] 
                      ] |> Map.ofList
    let transform = genrateRecordTransformFunction<Person> (FungibleCoreSettings.Default) functionMap
    testPersons |> List.map transform

What if we want to apply multiple different transforms to the same field? Just list the transforms and they'll be executed in order.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let removeUpperVowelsExpr = 
    let removeUpperVowels (s: string) =
        let vowels = [|'A'; 'E'; 'I'; 'O'; 'U'|] // TODO: Sometimes Y?
        s |> String.filter (fun c -> Array.contains c vowels |> not)
    <@@ removeUpperVowels @@> |> Map

let ex4 = 
    let functionMap = [(["City"; "Locations"], [ upperFunMapExpr; removeUpperVowelsExpr ])] |> Map.ofList
    let transform = genrateRecordTransformFunction<Person> (FungibleCoreSettings.Default) functionMap
    testPersons |> List.map transform

let ex5 = 
    let functionMap = [(["City"; "Locations"], [ removeUpperVowelsExpr; upperFunMapExpr ])] |> Map.ofList
    let transform = genrateRecordTransformFunction<Person> (FungibleCoreSettings.Default) functionMap
    testPersons |> List.map transform

Let's dig into the static reflection part of the library, which is used for pulling functions in from F# libraries based on a settings spec.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
open Fungible.StaticReflection

module TestFunctions = 
    open System
    open System.Text
    open System.ComponentModel

    type internal Marker = interface end
    let ModuleType = typeof<Marker>.DeclaringType

    [<TransformFunctionType("Map")>]
    [<Description("Removes the specified characters from the given string")>]
    let removeCharacters (toRemove: char []) (inStr: string) =    
        let sb = new StringBuilder()
        for c in inStr do            
            if Array.IndexOf(toRemove, c) = -1 then sb.Append(c) |> ignore
        sb.ToString()   

let ex6_staticTransforms = 
    let settings =  [| { TargetPath = "Names"; 
                         FunctionName = "removeCharacters"; 
                         FunctionArgs = [| "R"; "r" |] } |]
    let propertyMap = getPathsAndTypes<Person>()
    let generator = generateTransform TestFunctions.ModuleType propertyMap
    settings |> Array.map generator

let ex6 =
    let compiledTransforms = compileTransforms<Person, Person> ex6_staticTransforms
    testPersons |> List.map (fun p -> compiledTransforms p p)

Next let's explore using Barb functions with the Barb extension library.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open Barb.Representation
open Fungible.Barb

let ex7_barbTransforms = 
    let barbSettings = BarbSettings.Default
    let barbDefs = [| { TargetPath = "Names"
                        Kind = "Map"
                        Function = "Value.ToLower()" } |]
    let propertyMap = getPathsAndTypes<Person>()
    let generator = generateBarbTransform<Person> barbSettings propertyMap
    barbDefs |> Array.map generator

let ex7 =  
    let compiledTransforms = compileTransforms<Person, Person> ex7_barbTransforms
    testPersons |> List.map (fun p -> compiledTransforms p p)

You can mix Barb and Static Reflection transforms interchangeably.

1: 
2: 
3: 
4: 
let ex8 = 
    let mixedTransforms = Array.append ex7_barbTransforms ex6_staticTransforms
    let compiledTransforms = compileTransforms<Person, Person> mixedTransforms
    testPersons |> List.map (fun p -> compiledTransforms p p)
namespace Fungible
module Core

from Fungible
type Date =
  {Day: byte;
   Month: byte;
   Year: uint16;}

Full name: Tutorial.Date
Date.Day: byte
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = System.Byte

Full name: Microsoft.FSharp.Core.byte
Date.Month: byte
Date.Year: uint16
Multiple items
val uint16 : value:'T -> uint16 (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.uint16

--------------------
type uint16 = System.UInt16

Full name: Microsoft.FSharp.Core.uint16
type Location =
  {Address: string option;
   City: string option;
   State: string option;
   Country: string option;}

Full name: Tutorial.Location
Location.Address: string option
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
Location.City: string option
Location.State: string option
Location.Country: string option
type Person =
  {Names: string [];
   DOBs: Date [];
   Citizenships: string [];
   Locations: Location [];
   Fields: Map<string,string>;}

Full name: Tutorial.Person
Person.Names: string []
Person.DOBs: Date []
Person.Citizenships: string []
Person.Locations: Location []
Person.Fields: Map<string,string>
Multiple items
union case FieldAction.Map: Quotations.Expr -> FieldAction

--------------------
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val testPersons : Person list

Full name: Tutorial.testPersons
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
val upperFunMapExpr : FieldAction

Full name: Tutorial.upperFunMapExpr
val upperFun : (string -> string)
val s : string
System.String.ToUpper() : string
System.String.ToUpper(culture: System.Globalization.CultureInfo) : string
val ex1 : Person list

Full name: Tutorial.ex1
val functionMap : Map<string list,FieldAction list>
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
val transform : (Person -> Person)
val genrateRecordTransformFunction : settings:FungibleCoreSettings -> updaters:FieldUpdaters -> ('T -> 'T)

Full name: Fungible.Core.genrateRecordTransformFunction
type FungibleCoreSettings =
  {CloneWhenNoChanges: bool;
   FailOnUnsupportedType: bool;}
  static member Default : FungibleCoreSettings

Full name: Fungible.Core.FungibleCoreSettings
property FungibleCoreSettings.Default: FungibleCoreSettings
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val ex2 : Person list

Full name: Tutorial.ex2
val ex3 : Person list

Full name: Tutorial.ex3
val removeUpperVowelsExpr : FieldAction

Full name: Tutorial.removeUpperVowelsExpr
val removeUpperVowels : (string -> string)
val vowels : char []
module String

from Microsoft.FSharp.Core
val filter : predicate:(char -> bool) -> str:string -> string

Full name: Microsoft.FSharp.Core.String.filter
val c : char
module Array

from Microsoft.FSharp.Collections
val contains : value:'T -> array:'T [] -> bool (requires equality)

Full name: Microsoft.FSharp.Collections.Array.contains
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val ex4 : Person list

Full name: Tutorial.ex4
val ex5 : Person list

Full name: Tutorial.ex5
module StaticReflection

from Fungible
namespace System
namespace System.Text
namespace System.ComponentModel
type internal Marker

Full name: Tutorial.TestFunctions.Marker
val ModuleType : Type

Full name: Tutorial.TestFunctions.ModuleType
val typeof<'T> : Type

Full name: Microsoft.FSharp.Core.Operators.typeof
Multiple items
type TransformFunctionTypeAttribute =
  inherit Attribute
  new : ftype:String -> TransformFunctionTypeAttribute
  member Type : String
  member Type : String with set

Full name: Fungible.StaticReflection.TransformFunctionTypeAttribute

--------------------
new : ftype:String -> TransformFunctionTypeAttribute
Multiple items
type DescriptionAttribute =
  inherit Attribute
  new : unit -> DescriptionAttribute + 1 overload
  member Description : string
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member IsDefaultAttribute : unit -> bool
  static val Default : DescriptionAttribute

Full name: System.ComponentModel.DescriptionAttribute

--------------------
DescriptionAttribute() : unit
DescriptionAttribute(description: string) : unit
val removeCharacters : toRemove:char [] -> inStr:string -> string

Full name: Tutorial.TestFunctions.removeCharacters
val toRemove : char []
Multiple items
val char : value:'T -> char (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.char

--------------------
type char = Char

Full name: Microsoft.FSharp.Core.char
val inStr : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val sb : StringBuilder
Multiple items
type StringBuilder =
  new : unit -> StringBuilder + 5 overloads
  member Append : value:string -> StringBuilder + 18 overloads
  member AppendFormat : format:string * arg0:obj -> StringBuilder + 4 overloads
  member AppendLine : unit -> StringBuilder + 1 overload
  member Capacity : int with get, set
  member Chars : int -> char with get, set
  member Clear : unit -> StringBuilder
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EnsureCapacity : capacity:int -> int
  member Equals : sb:StringBuilder -> bool
  ...

Full name: System.Text.StringBuilder

--------------------
StringBuilder() : unit
StringBuilder(capacity: int) : unit
StringBuilder(value: string) : unit
StringBuilder(value: string, capacity: int) : unit
StringBuilder(capacity: int, maxCapacity: int) : unit
StringBuilder(value: string, startIndex: int, length: int, capacity: int) : unit
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
Array.IndexOf<'T>(array: 'T [], value: 'T) : int
Array.IndexOf(array: Array, value: obj) : int
Array.IndexOf<'T>(array: 'T [], value: 'T, startIndex: int) : int
Array.IndexOf(array: Array, value: obj, startIndex: int) : int
Array.IndexOf<'T>(array: 'T [], value: 'T, startIndex: int, count: int) : int
Array.IndexOf(array: Array, value: obj, startIndex: int, count: int) : int
StringBuilder.Append(value: char []) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: obj) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: uint64) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: uint32) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: uint16) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: decimal) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: float) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: float32) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: int64) : StringBuilder
   (+0 other overloads)
StringBuilder.Append(value: int) : StringBuilder
   (+0 other overloads)
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
StringBuilder.ToString() : string
StringBuilder.ToString(startIndex: int, length: int) : string
val ex6_staticTransforms : Transform []

Full name: Tutorial.ex6_staticTransforms
val settings : TransformDefinition []
val propertyMap : Map<string list,System.Type>
val getPathsAndTypes<'t> : unit -> Map<string list,System.Type>

Full name: Fungible.Core.getPathsAndTypes
val generator : (TransformDefinition -> Transform)
val generateTransform<'U> : functionModule:System.Type -> propertyMap:Map<string list,System.Type> -> basic:TransformDefinition -> Transform

Full name: Fungible.StaticReflection.generateTransform
module TestFunctions

from Tutorial
val ModuleType : System.Type

Full name: Tutorial.TestFunctions.ModuleType
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val ex6 : Person list

Full name: Tutorial.ex6
val compiledTransforms : (Person -> Person -> Person)
val compileTransforms : transforms:seq<Transform> -> ('U -> 'T -> 'T)

Full name: Fungible.StaticReflection.compileTransforms
val p : Person
Multiple items
module Barb

from Fungible

--------------------
namespace Barb
module Representation

from Barb
module Barb

from Fungible
val ex7_barbTransforms : (string list * FieldAction) []

Full name: Tutorial.ex7_barbTransforms
val barbSettings : BarbSettings
type BarbSettings =
  {BindGlobalsWhenReducing: bool;
   FailOnCatchAll: bool;
   Namespaces: Set<string>;
   AdditionalBindings: IDictionary<string,obj>;}
  static member Default : BarbSettings

Full name: Barb.Representation.BarbSettings
property BarbSettings.Default: BarbSettings
val barbDefs : BarbTransformDefinition []
union case FieldAction.Function: Quotations.Expr -> FieldAction
val generator : (BarbTransformDefinition -> string list * FieldAction)
val generateBarbTransform<'U> : barbSettings:BarbSettings -> propertyMap:Map<string list,System.Type> -> transformDef:BarbTransformDefinition -> string list * FieldAction

Full name: Fungible.Barb.generateBarbTransform
val ex7 : Person list

Full name: Tutorial.ex7
val ex8 : Person list

Full name: Tutorial.ex8
val mixedTransforms : (string list * FieldAction) []
val append : array1:'T [] -> array2:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.append
Fork me on GitHub