Skip to content

Instantly share code, notes, and snippets.

@ImaginaryDevelopment
Last active January 18, 2017 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ImaginaryDevelopment/0b9908c0cc5789e20d0817ef92b66b73 to your computer and use it in GitHub Desktop.
Save ImaginaryDevelopment/0b9908c0cc5789e20d0817ef92b66b73 to your computer and use it in GitHub Desktop.
[<AutoOpen>]
module Pm.Schema.BReusable
open System
open System.Collections.Generic
open System.Diagnostics
//consider pulling in useful functions from https://gist.github.com/ruxo/a9244a6dfe5e73337261
// long pipe chains don't allow breakpoints anywhere inside
// does this need anything to prevent the method from being inlined/optimized away?
let breakpoint x =
let result = x
result
let breakpointf f x =
let result = f x
result
// based on http://stackoverflow.com/a/2362114/57883
// mimic the C# as keyword
let castAs<'t> (o:obj): 't option =
match o with
| :? 't as x -> Some x
| _ -> None
// active pattern, based on http://stackoverflow.com/a/25243799/57883
//let (|As|) (p:'T) : 'U option =
// let p = p :> obj
// if p :? 'U then Some (p :?> 'U) else None
// for statically typed parameters in an active pattern see: http://stackoverflow.com/questions/7292719/active-patterns-and-member-constraint
/// super handy with operators like (*) and (-)
/// take a function that expects 2 arguments and flips them before applying to the function
let inline flip f x y = f y x
/// take a tuple and apply the 2 arguments one at a time (from haskell https://www.haskell.org/hoogle/?hoogle=uncurry)
let uncurry f (x,y) = f x y
/// take a dead-end function and curry the input
let inline tee f x = f x; x
/// does not work with null x
let inline getType x = x.GetType()
let inline isNullOrEmptyToOpt s =
if String.IsNullOrEmpty s then None else Some s
// was toFormatString
// with help from http://www.readcopyupdate.com/blog/2014/09/26/type-constraints-by-example-part1.html
let inline toFormatString (f:string) (a:^a) = ( ^a : (member ToString:string -> string) (a,f))
//if more is needed consider humanizer or inflector
let toPascalCase s =
s
|> Seq.mapi (fun i l -> if i=0 && Char.IsLower l then Char.ToUpper l else l)
|> String.Concat
let humanize camel :string =
seq {
let pascalCased = toPascalCase camel
yield pascalCased.[0]
for l in pascalCased |> Seq.skip 1 do
if System.Char.IsUpper l then
yield ' '
yield l
else
yield l
}
|> String.Concat
module Debug =
open System.Collections.ObjectModel
type FListener(fWrite: _ -> unit,fWriteLn:_ -> unit, name) =
inherit TraceListener(name)
override __.Write (msg:string) = fWrite msg
override __.WriteLine (msg:string) = fWriteLn msg
new(fWrite,fWriteLn) = new FListener(fWrite,fWriteLn, null)
type FLineListener(source:string ObservableCollection, fLineMap) =
inherit TraceListener()
let mutable lastWasWriteNotWriteLine = false
let fLineMap = defaultArg fLineMap id
let addText msg isLineFinished =
if lastWasWriteNotWriteLine then
let lastLine = source.[source.Count - 1]
assert (source.Remove lastLine)
lastLine + msg
else msg
|> fun x -> if isLineFinished then fLineMap x else x
|> source.Add
new(source, lineMap:Func<_, _>) = new FLineListener(source,fLineMap = if isNull lineMap then None else Some lineMap.Invoke)
override __.Write (msg:string) =
addText msg false
lastWasWriteNotWriteLine <- true
override __.WriteLine (msg:string) =
addText msg true
lastWasWriteNotWriteLine <- false
type DebugTraceListener(?breakOnAll) =
inherit TraceListener()
let mutable breakOnAll:bool = defaultArg breakOnAll false
override __.Write (_msg:string) = ()
override __.WriteLine (msg:string) =
let toIgnorePatterns = [
@"BindingExpression path error: 'Title' property not found on 'object' ''String' \(HashCode=-[0-9]+\)'. BindingExpression:Path=Title; DataItem='String' \(HashCode=-[0-9]+\); target element is 'ContentPresenter' \(Name='Content'\); target property is 'ResourceKey' \(type 'String'\)"
]
let regMatch p =
let m = Text.RegularExpressions.Regex.Match(msg,p)
if m.Success then
Some p
else
None
let matchedIgnorePattern = toIgnorePatterns |> Seq.choose regMatch |> Seq.tryHead
match matchedIgnorePattern with
| Some _ -> ()
| None ->
if breakOnAll && Debugger.IsAttached then
Debugger.Break()
else ()
type Listener(created:DateTime, name) =
inherit TraceListener(name)
new(created) = new Listener(created, null)
override __.Write (msg:string) = printf "%s" msg
override __.WriteLine (msg:string) =
printfn "%s" msg
member __.Created= created
let inline assertIfDebugger b =
if not b then
printfn "Assertion failed"
if Diagnostics.Debugger.IsAttached then
Debugger.Break()
// this option may be different from Debug.Assert somehow
// https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/assertions
// else
// assert b
module Tuple2 = // idea and most code taken from https://gist.github.com/ploeh/6d8050e121a5175fabb1d08ef5266cd7
let replicate x = x,x
// useful for Seq.mapi
let fromCurry x y = (x,y)
let curry f x y = f (x, y)
// calling already defined function from outer namespace, instead of duplicating the functionality for consistency with gist
let uncurry f (x, y) = uncurry f (x, y)
let swap (x, y) = (y, x)
let mapFst f (x, y) = f x, y
let mapSnd f (x, y) = x, f y
let extendFst f (x,y) = f (x,y), y
let extendSnd f (x,y) = x, f(x,y)
let optionOfFst f (x,y) =
match f x with
| Some x -> Some (x, y)
| None -> None
let optionOfSnd f (x,y) =
match f y with
| Some y -> Some (x,y)
| None -> None
type System.Action with
static member invoke (x:System.Action) () = x.Invoke()
static member invoke1 (x:System.Action<_>) y = x.Invoke(y)
static member invoke2 (x:System.Action<_,_>) y z = x.Invoke(y,z)
static member invoke3 (x:System.Action<_,_,_>) a b c = x.Invoke(a,b,c)
type System.Func<'tResult> with
static member invoke (x:System.Func<'tResult>) () = x.Invoke()
static member invoke1<'t> (x:System.Func<'t,'tResult>) y = x.Invoke y
static member invoke2<'t1,'t2> (x:System.Func<'t1,'t2,'tResult>) y z = x.Invoke(y, z)
static member invoke3 (x:System.Func<'t1,'t2,'t3,'tResult>) a b c = x.Invoke(a,b,c)
static member invoke4 (x:System.Func<'t1, 't2, 't3, 't4, 'tResult>) a b c d = x.Invoke(a,b,c,d)
//type System.Action<'t> with
// static member invoke (x:Action<'t>) (y:'t) = y |> x.Invoke
//module Array =
// let ofOne x = [| x |]
module Seq =
// Seq.take throws if there are no items
let takeLimit limit =
let mutable count = 0
Seq.takeWhile(fun _ ->
let result = count < limit
count <- count + 1
result)
let any items = items |> Seq.exists(fun _ -> true)
let copyFrom (source: _ seq) (toPopulate:IList<_>) =
if not <| isNull source && not <| isNull toPopulate then
use enumerator = source.GetEnumerator()
while enumerator.MoveNext() do
toPopulate.Add(enumerator.Current)
let ofType<'t> items =
items |> Seq.cast<obj> |> Seq.choose (fun x -> match x with | :? 't as x -> Some x | _ -> None )
module List =
// is this worth having/keeping?
// let except toScrape items =
// let toScrape = Set.ofList toScrape
// let items = Set.ofList items
// items - toScrape
// return a Tuple where (A, B) (both present if they have a match)
let forceJoin b a =
let b = Set.ofList b
let a = Set.ofList a
let x = Set.intersect a b
let diffa = a - b
let diffb = b - a
diffa - x
|> Seq.map (fun a' -> Some a', None)
|> Seq.append (x |> Seq.map (fun x' -> (Some x', Some x')))
|> Seq.append (diffb - x |> Seq.map (fun b' -> None, Some b'))
|> List.ofSeq
module Observables =
open System.Collections.ObjectModel
open System.Collections.Specialized
let bindObsTToObsObjDispatched (obsCollection:ObservableCollection<'t>) fDispatch =
let obsObj = ObservableCollection<obj>()
obsCollection.CollectionChanged.Add (fun e ->
match e.Action with
|NotifyCollectionChangedAction.Add ->
fDispatch (fun () ->
e.NewItems
|> Seq.cast<obj>
|> Seq.iter obsObj.Add
)
|NotifyCollectionChangedAction.Move ->
fDispatch (fun () ->
let oldIndex = e.OldStartingIndex
let newIndex = e.NewStartingIndex
obsObj.Move(oldIndex,newIndex)
)
|NotifyCollectionChangedAction.Remove ->
fDispatch (fun () ->
e.OldItems
|> Seq.cast<obj>
|> Seq.iter (obsObj.Remove>> ignore<bool>)
)
|NotifyCollectionChangedAction.Replace ->
fDispatch (fun () ->
e.NewItems
|> Seq.cast<obj>
|> Seq.zip (e.OldItems |> Seq.cast<obj>)
|> Seq.iteri(fun i (oldItem,newItem) ->
assert (obsObj.[e.OldStartingIndex + i] = oldItem)
obsObj.[e.OldStartingIndex + i] <- newItem
)
)
| NotifyCollectionChangedAction.Reset ->
fDispatch (fun () ->
obsObj.Clear()
if not <| isNull e.NewItems then
e.NewItems
|> Seq.cast<obj>
|> Seq.iter obsObj.Add
)
| x -> failwithf "Case %A is unimplemented" x
)
obsObj
let bindObsTToObsObj (obsCollection:ObservableCollection<'t>) =
bindObsTToObsObjDispatched obsCollection (fun f -> f())
// |Null|Value| already in use by Nullable active pattern
type System.Convert with
static member ToGuid(o:obj) = o :?> Guid
static member ToBinaryData(o:obj) = o :?> byte[] // http://stackoverflow.com/a/5371281/57883
// I've been fighting/struggling with where to namespace/how to architect string functions, they are so commonly used, static members make it easier to find them
// since typing `String.` with this module open makes them all easy to find
type System.String with
static member trim (s:string) = match s with | null -> null | s -> s.Trim()
static member defaultComparison = StringComparison.InvariantCultureIgnoreCase
static member Null:string = null
static member emptyToNull (x:string) = if String.IsNullOrEmpty x then null else x
//let replace (target:string) (r:string) (x:string) = if String.IsNullOrEmpty target then invalidOp "bad target" else x.Replace(target,r)
// static member replace (target:string) (replacement) (str:string) = if String.IsNullOrEmpty target then invalidOp "bad target" else str.Replace(target,replacement)
static member equalsI (x:string) (x2:string) = not <| isNull x && not <| isNull x2 && x.Equals(x2, StringComparison.InvariantCultureIgnoreCase)
// static member contains (sub:string) (x:string) = if isNull x then false elif isNull sub || sub = "" then failwithf "bad contains call" else x.IndexOf(sub, String.defaultComparison) >= 0
static member startsWithI (toMatch:string) (x:string) = not <| isNull x && not <| isNull toMatch && toMatch.Length > 0 && x.StartsWith(toMatch, String.defaultComparison)
static member isNumeric (x:string) = not <| isNull x && x.Length > 0 && x |> String.forall Char.IsNumber
// static member before (delimiter:string) (x:string) = x.Substring(0, x.IndexOf delimiter)
// static member after (delimiter:string) (x:string) =
// match x.IndexOf delimiter with
// | i when i < 0 -> failwithf "after called without matching substring in '%s'(%s)" x delimiter
// | i -> x.Substring(i + delimiter.Length)
static member split (items:string seq) options (x:string) = x.Split(items |> Array.ofSeq, options)
static member splitLines(x:string) = x.Split([| "\r\n";"\n"|], StringSplitOptions.None)
static member beforeAnyOf (delimiters:string list) (x:string) =
let index, _ =
delimiters
|> Seq.map (fun delimiter -> x.IndexOf(delimiter),delimiter)
|> Seq.filter(fun (index,_) -> index >= 0)
|> Seq.minBy (fun (index, _) -> index)
x.Substring(0,index)
// I've also been struggling with the idea that Active patterns are frequently useful as just methods, so sometimes methods are duplicated as patterns
[<AutoOpen>]
module StringPatterns =
let (|NullString|Empty|WhiteSpace|ValueString|) (s:string) =
match s with
| null -> NullString
| "" -> Empty
| _ when String.IsNullOrWhiteSpace s -> WhiteSpace
| _ -> ValueString
let (|StartsWith|_|) (str:string) arg = if str.StartsWith(arg) then Some() else None
let (|StartsWithI|_|) s1 (toMatch:string) = if toMatch <> null && toMatch.StartsWith(s1, StringComparison.InvariantCultureIgnoreCase) then Some () else None
let (|StringEqualsI|_|) s1 (toMatch:string) = if String.equalsI toMatch s1 then Some() else None
let (|InvariantEqualI|_|) (str:string) arg =
if String.Compare(str, arg, StringComparison.InvariantCultureIgnoreCase) = 0
then Some() else None
let (|IsNumeric|_|) (s:string) = if not <| isNull s && s.Length > 0 && s |> String.forall Char.IsNumber then Some() else None
let (|OrdinalEqualI|_|) (str:string) arg =
if String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0
then Some() else None
let inline (|IsTOrTryParse|_|) (t,parser) (x:obj): 't option =
match x with
| v when v.GetType() = t -> Some (v :?> 't)
| :? string as p ->
match parser p with
| true, v -> Some v
| _, _ -> None
| _ -> None
let (|Int|_|) (x:obj) =
match x with
| :? string as p ->
let success,value = System.Int32.TryParse(p)
if success then
Some value
else None
| _ -> None
// not having to type `String.` on at least the used constantly is a huge reduction in typing
// also helps with point-free style
[<AutoOpen>]
module StringHelpers =
open StringPatterns
let contains (sub:string) (x:string) = if isNull x then false elif isNull sub || sub = "" then failwithf "bad contains call" else x.IndexOf(sub, String.defaultComparison) >= 0
let containsI (sub:string) (x:string) = if isNull x then false elif isNull sub || sub = "" then failwithf "bad contains call" else x.IndexOf(sub, String.defaultComparison) >= 0
let trim = String.trim
let replace (target:string) (replacement) (str:string) = if String.IsNullOrEmpty target then invalidOp "bad target" else str.Replace(target,replacement)
let delimit delimiter (values:#seq<string>) = String.Join(delimiter, Array.ofSeq values)
let after (delimiter:string) (x:string) =
match x.IndexOf delimiter with
| i when i < 0 -> failwithf "after called without matching substring in '%s'(%s)" x delimiter
| i -> x.Substring(i + delimiter.Length)
let before (delimiter:string) (x:string) = x.Substring(0, x.IndexOf delimiter)
let afterI (delimiter:string) (x:string) = x.Substring(x.IndexOf(delimiter, String.defaultComparison) + delimiter.Length)
let afterOrSelf (delimiter:string) (x:string) = if x |> contains delimiter then x|> after delimiter else x
let afterOrSelfI (delimiter:string) (x:string) = if x |> containsI delimiter then x |> afterI delimiter else x
let substring i (x:string) = x.Substring i
let substring2 i length (x:string) = x.Substring(i, length)
let isValueString s =
match s with
| ValueString -> true
| _ -> false
// based on http://stackoverflow.com/questions/15115050/f-type-constraints-on-enums
type System.Enum with // I think enum<int> is the only allowed enum-ish constraint allowed in all of .net
static member parse<'t when 't : enum<int>> x = Enum.Parse(typeof<'t>,x)
static member parseT t x = Enum.Parse(t, x)
static member fromString<'t when 't:enum<int>> x = Enum.parse<'t> x :?> 't
static member getName<'t when 't : enum<int>> x = Enum.GetName(typeof<'t>,x)
static member getAll<'t when 't : enum<int>>() =
Enum.GetValues(typeof<'t>)
|> Seq.cast<int>
|> Seq.map (fun x -> Enum.getName<'t> x)
|> Seq.map (fun x -> Enum.parse<'t> x :?> 't)
static member fromInt<'t when 't :enum<int>>(i:int) =
Enum.getName<'t> i
|> fun x -> Enum.parse<'t> x :?> 't
type System.DateTime with
static member addDays dy (dt:DateTime) = dt.AddDays dy
static member addHours h (dt:DateTime) = dt.AddHours h
static member addMinutes min (dt:DateTime) = dt.AddMinutes min
static member toShortDateString (dt:DateTime) = dt.ToShortDateString()
// taken from SO http://stackoverflow.com/a/1595311/57883
static member getAge (now:DateTime) (dt:DateTime) =
let age = now.Year - dt.Year
if (now.Month < dt.Month || (now.Month = dt.Month && now.Day < dt.Day)) then
age - 1
else
age
static member toString (format:string) (dt:DateTime) = dt.ToString(format)
//public static string CalculateAge(DateTime birthDate, DateTime now)
static member getAgeDisplay (now:DateTime) (dob:DateTime) =
let years = DateTime.getAge now dob
let _days,now =
let days = now.Day - dob.Day
if days < 0 then
let newNow = now.AddMonths(-1)
let totalDays = now - newNow
let totalDays = int totalDays.TotalDays
days + totalDays,newNow
else days,now
let months = ((now.Year - dob.Year) * 12) + now.Month - dob.Month
if (years <= 2) then
months.ToString() + "m"
else
years.ToString() + "y"
member x.GetAge (now:DateTime) = DateTime.getAge now x
type System.TimeSpan with
static member getTicks (x:TimeSpan) = x.Ticks
static member toString (s:string) (x:TimeSpan) = x.ToString(s)
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>] // for C# access
module DateTime =
let getAgeDisplay now dob = DateTime.getAgeDisplay now dob
// Railway Oriented Programming
type Rail<'tSuccess,'tFailure> =
|Happy of 'tSuccess
|Unhappy of 'tFailure
[<RequireQualifiedAccess>]
module Railway =
/// apply either a success function or a failure function
let inline either happyFunc unhappyFunc twoTrackInput =
match twoTrackInput with
|Happy s -> happyFunc s
|Unhappy u -> unhappyFunc u
/// convert a one-track function into a switch
let inline switch f = f >> Happy
/// convert a switch function into a two-track function
let inline bind f = either f Unhappy
// convert a one-track function into a two-track function
let inline map f = //why isn't this simply "bind (f >> Happy)" ?
either (f >> Happy) Unhappy
/// bind a function to the failure track
/// primary design purpose: adding data to the failure track
let inline bind' f = either (Happy) f
let toHappyOption =
function
| Happy s -> s |> Some
| _ -> None
/// An adapter that takes a normal one-track function and turns it into a switch function, and also catches exceptions
/// could use id instead of a full exnHandler function for cases you just want the exception
let inline tryCatch f exnHandler x =
try
f x |> Happy
with ex -> exnHandler ex |> Unhappy
module Rop =
type Error = {Property : string; Message : string}
type Result<'a> =
| Success of 'a
| Fail of Error
let bind f x =
match x with Success x -> f x |Fail err -> Fail err
let bind' f1 f2 x =
match f1 x with
| Success x -> f2 x
| Fail err -> Fail err
// let inline (>>=) f1 f2 = bind' f1 f2
let overrideFail default' r = match r with |Success x -> x | Fail(_) -> default'
let overrideFail' f r = match r with |Success x -> x | Fail(_) -> f()
//http://stackoverflow.com/a/8227943/57883
let lock (lockobj:obj) f =
System.Threading.Monitor.Enter lockobj
try
f()
finally
System.Threading.Monitor.Exit lockobj
let buildCmdString fs arg i :string*string*obj =
let applied = sprintf fs arg
let replacement = (sprintf"{%i}" i)
let replace target = replace target replacement
let replaced =
fs.Value
|> replace "'%s'"
|> replace "'%i'"
|> replace "'%d'"
applied,replaced, upcast arg
let inline SetAndNotify eq setter notifier=
if eq() then false
else
setter()
notifier()
true
let inline SetAndNotifyEquality field value setter notifier =
let eq () = EqualityComparer<'T>.Default.Equals(field, value)
SetAndNotify eq setter notifier
let SetAndNotifyEqualityC (field:'a byref, value:'a, notifier:System.Action) =
if EqualityComparer<'a>.Default.Equals(field,value) then
false
else
field <- value
notifier.Invoke()
true
module Diagnostics =
open System.Diagnostics
let tryAsyncCatch f =
f
|> Async.Catch
|> Async.Ignore
|> Async.Start
let filename (dt:DateTime) =
let pid =
try
let proc = System.Diagnostics.Process.GetCurrentProcess()
sprintf "%i_%i" proc.Id proc.SessionId
with _ -> "0_0"
let dt = dt.ToString("yyyyMMdd")
sprintf "DebugLog_%s_%s.txt" dt pid
let fileLog filename (dt:DateTime) topic attrs s =
let attrs = (sprintf "dt=\"%A\"" dt)::attrs |> delimit " "
let topic = match topic with |Some t -> t |_ -> "Message"
let msg = sprintf "<%s %s>%s</%s>%s" topic attrs s topic Environment.NewLine
System.IO.File.AppendAllText(filename,msg)
let logS topic attrs s =
if String.IsNullOrEmpty s = false then
printfn "%s" s
Debug.WriteLine s
let dt = DateTime.Now
let filename = filename(dt)
let fileLog'= fileLog filename dt topic attrs
try
fileLog' s
with |ex ->
printfn "Exception attemping to log:%A" ex
let LogS topic s =
logS (isNullOrEmptyToOpt topic) [] s
let logEx topic (ex:exn) = sprintf "%A" ex |> logS topic []
let LogEx topic (ex:exn) = logEx (isNullOrEmptyToOpt topic) ex
let logExS topic s ex = sprintf "%s:%A" s ex |> logS topic []
let LogExS topic s ex = logExS (isNullOrEmptyToOpt topic) s ex
let BeginLogScope scopeName=
let pid = Process.GetCurrentProcess().Id
logS (Some scopeName) [ sprintf "pid=\"%i\"" pid ] ""
{ new System.IDisposable
with member __.Dispose() = logS (Some scopeName) [] (sprintf "<%s/>" scopeName)
}
let BeginTimedLogScope scopeName=
let pid = Process.GetCurrentProcess().Id
let sw = Stopwatch.StartNew()
logS (Some scopeName) [ sprintf "pid=\"%i\"" pid ] ""
{
new System.IDisposable
with member __.Dispose() =
sw.Stop()
logS (Some scopeName) [] (sprintf " <Elapsed>%A</Elapsed>" sw.ElapsedMilliseconds)
logS (Some scopeName) [] (sprintf "<%s/>" scopeName)
}
let addDataMaybe k v (ex:#exn) =
if not <| ex.Data.Contains k then
ex.Data.Add (k, v)
let tryDataAdd (_:#exn) f =
try
f()
with ex ->
logEx (Some "error adding exception data") ex
module Option = // https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/option.fs
/// unsafe (Unchecked.defaultof<_>)
let getValueOrDefault (n: 'a option) = match n with | Some x -> x | None -> Unchecked.defaultof<_>
// the built-in exists, but the order here is more natural
let getOrDefault (default': 'a) (n: 'a option) = match n with| Some x -> x | None -> default'
let getOrDefault' (default': 'a Lazy) (n: 'a option) = match n with| Some x -> x | None -> default'.Force()
// for types the compiler insists aren't nullable, but maybe C# is calling
let ofUnsafeNonNullable x =
match box x with
| null -> None
| _ -> Some x
// primarily for C# / wpf where the framework/ui are the only ones not accounting for this
let toUnsafeObj x =
match x with
| Some x -> box x
| None -> null
module Reflection =
open System.Reflection
open Microsoft.FSharp.Reflection
let rec compareProps goDeep asTypeOpt blacklist nameOpt expected actual =
let doNotDescendTypes = [typeof<string>; typeof<DateTime>; typeof<Type>;]
// for types we don't want to take a reference to, but should not be descended
let doNotDescendTypeNames = ["System.Windows.Threading.Dispatcher";"System.Windows.DependencyObjectType";"System.Windows.Media.Transform"]
let t = asTypeOpt |> Option.getOrDefault' (Lazy(Func<_>(fun () -> expected.GetType())))
let props = t.GetProperties()
let toWalk = props |> Seq.filter(fun p -> blacklist |> Seq.contains p.Name |> not && p.GetMethod.GetParameters().Length = 0)
let blacklist = blacklist |> Seq.except (props |> Seq.map (fun p -> p.Name)) |> List.ofSeq
seq{
for p in toWalk do
printfn "Getting property %s via type %s(%s)" p.Name t.Name t.FullName
let valuesOpt =
try
Some (p.GetValue expected , p.GetValue actual)
with ex ->
ex.Data.Add("Type", t.Name)
ex.Data.Add("TypeFullName", t.FullName)
None
match valuesOpt with
| Some (expected,actual) ->
if goDeep
&& not <| isNull expected
&& not <| isNull actual
&& doNotDescendTypes |> Seq.contains p.PropertyType |> not
&& doNotDescendTypeNames |> Seq.contains p.PropertyType.FullName |> not
then
printfn "Going deep into %s via type %s(%s)" p.Name t.Name t.FullName
let bindingName = match nameOpt with | Some n -> sprintf "%s.%s" n p.Name | None -> p.Name
yield! compareProps true (Some p.PropertyType) blacklist (Some bindingName) expected actual
let bindingName = match nameOpt with | Some n -> sprintf "%s.%s" n p.Name | None -> p.Name
yield ((expected = actual), bindingName, p.PropertyType, sprintf "%A: expected %A, actual was %A" p.Name expected actual)
| None -> ()
}
let rec typeMatch t (g:Type) =
if t = typeof<obj> then
None
elif g.IsInterface then
let ints = if t.IsInterface then [| t |] else t.GetInterfaces()
ints |> Seq.tryPick (fun t -> if t.GetGenericTypeDefinition() = g then Some(t.GetGenericArguments()) else None)
elif t.IsGenericType && t.GetGenericTypeDefinition() = g then
t.GetGenericArguments() |> Some
else typeMatch (t.BaseType) g
/// for when you need to see if something matches and expected Generic Type Definition ( you don't know "'t" but don't care)
/// Sample (tested good) usage:
/// match list with
/// | TypeDefOf (isType:List<_>) typeArgs -> sprintf "Yay matched1 : %A" typeArgs \r\n
/// | _ -> "boo"
/// Also works for some types:
/// | TypeDefOf (null:List<_>) typeArgs -> sprintf "Yay matched: %A" typeArgs
let (|TypeDef|_|) (_:'a) (value:obj) =
let typeDef = typedefof<'a>
if obj.ReferenceEquals(value, null) then
None
else
let typ = value.GetType()
if typ.Name = "RuntimeType" then failwithf "Invalid use of |TypeDef|"
// let gtd = if typ.IsGenericType then typ.GetGenericTypeDefinition() |> Some else None
if typ.IsGenericType && typ.GetGenericTypeDefinition() = typeDef then
Some(typ.GetGenericArguments())
else
let typeArgs = typeMatch typ typeDef
typeArgs
// for when you don't have a value or you want a switch on an instance of Type
// or you want to unbox as one of a number of possible types
// do not use where `| :?` is appropriate
let (|TypeOf|_|) (_:'a) t =
if t = typeof<'a> then Some ()
else
//printfn "did not match %A to %A" typeof<'a> t
None
// instead of null in TypeOf or TypeDef matches for types that don't allow null
let isType<'a> = Unchecked.defaultof<'a>
let rec getMethod recurse name (t:Type) =
seq {
let m = t.GetMethod(name)
if not <| isNull m then
yield t,m
if recurse then
yield! t.GetInterfaces() |> Seq.collect (getMethod recurse name)
}
let rec getMethods recurse (t:Type) =
seq {
yield (t,t.GetMethods())
if recurse then
yield! t.GetInterfaces() |> Seq.collect (getMethods recurse)
}
// primarily for use hand-in-hand with the 'Nullish' active pattern
//unhandled: _ Nullable option
/// for boxed objects that may be 'Valueable`
let rec getReflectionValueOpt (genTypeOpt:Type option) (typeOpt:Type option) (o:obj) =
match o,genTypeOpt, typeOpt with
| null, _, _ -> None
| _ , Some gt ,_ ->
// based on http://stackoverflow.com/a/13367848/57883
match gt.GetProperty "Value" with
| null -> None
| prop ->
let v = prop.GetValue(o,null)
Some v
| _, _,Some t ->
match t.IsGenericType with
| true -> getReflectionValueOpt typeOpt (t.GetGenericTypeDefinition() |> Some) o
| false -> Some o
| _, _, None ->
getReflectionValueOpt None (o.GetType() |> Some) o
//method taken from http://stackoverflow.com/q/4604139/57883
let methodSourceName (mi:MemberInfo) =
mi.GetCustomAttributes(true)
|> Array.tryPick
(function
| :? CompilationSourceNameAttribute as csna -> Some(csna)
| _ -> None)
|> (function | Some(csna) -> csna.SourceName | None -> mi.Name)
//currently this method is only called for diagnostic purposes
let rec getAllDUCases fNonUnionArg t : obj list =
let getAllDUCases = getAllDUCases fNonUnionArg
// both of the following are taken from http://stackoverflow.com/questions/6497058/lazy-cartesian-product-of-multiple-sequences-sequence-of-sequences
let cartesian_product sequences =
#if FSHARP44
let step acc sequence = seq {
for x in acc do
for y in sequence do
yield seq { yield! x; yield y}}
Seq.fold step (Seq.singleton Seq.empty) sequences
#else // TODO: F# 4.3 implementation
sequences |> ignore
Array.empty
// original from SO?
// let cartesian_product sequences =
// let step acc sequence = seq {
// for x in acc do
// for y in sequence do
// yield Seq.append x [y] }
// Seq.fold step (Seq.singleton Seq.empty) sequences
#endif
// only works with no arg cases I bet
let makeCaseTypes (fUnion:Type-> obj list) (fNonUnionArg:Type -> obj) (uc: UnionCaseInfo) : UnionCaseInfo*(obj list list) =
let constructorArgs =
uc.GetFields()
|> Seq.map (fun f ->
if FSharpType.IsUnion f.PropertyType then
let childTypes = fUnion f.PropertyType
if
childTypes
|> Seq.exists (fun ct -> FSharpType.IsUnion (ct.GetType()) |> not) then
failwithf "fUnion returned a bad type in list %A" childTypes
childTypes
else [ fNonUnionArg f.PropertyType] )
|> List.ofSeq
let allCombinationsOfFieldPossibles =
cartesian_product constructorArgs
|> Seq.map List.ofSeq
|> List.ofSeq
uc, allCombinationsOfFieldPossibles
// with help from http://stackoverflow.com/a/4470670/57883
// trying to write this one
let result =
FSharpType.GetUnionCases t
|> Seq.map (makeCaseTypes getAllDUCases fNonUnionArg)
|> List.ofSeq
let result =
result
|> Seq.map (fun (uc,allFieldComboCases) -> allFieldComboCases |> Seq.map (fun args-> FSharpValue.MakeUnion(uc,args |> Array.ofList)))
|> Seq.collect id
|> Seq.map box
|> List.ofSeq
result
module Assemblies =
// http://stackoverflow.com/a/28319367/57883
let getAssemblyFullPath (assembly:Assembly) =
let codeBaseFailedAssert () = Debug.Assert(false, "CodeBase evaluation failed! - Using Location as fallback.")
let fullPath =
match assembly.CodeBase with
| null -> codeBaseFailedAssert () ;assembly.Location
| codeBasePseudoUrl ->
let filePrefix3 = @"file:///"
if codeBasePseudoUrl.StartsWith filePrefix3 then
let sPath = codeBasePseudoUrl.Substring filePrefix3.Length
let bsPath = sPath.Replace('/', '\\')
bsPath
else codeBaseFailedAssert () ;assembly.Location
fullPath
open System.Linq.Expressions
open Microsoft.FSharp.Quotations.Patterns
// until we get the `nameof()` operator
module QuotationHelpers =
open Reflection
let rec getQuoteMemberName expr =
match expr with
|Call (_,mi,_) -> methodSourceName mi
|Lambda (_,expr) -> getQuoteMemberName expr
|Coerce(expr,_) -> getQuoteMemberName expr
|PropertyGet(_,p,_) -> p.Name
|FieldGet(_,fi) -> fi.Name
|ValueWithName(_,_,n) -> n
|_ -> failwithf "Method is not a call expression"
let getQuoteMemberNameT<'t> (expr:Quotations.Expr<'t -> _>) =
let expr = expr :> Quotations.Expr
getQuoteMemberName expr
let getTypeName<'t> =
match <@ fun (_:'t) -> () @> with
| Lambda(x,_expr) -> x.Type.Name
| x -> failwithf "getTypeName failed for %A" x
// this is unused, and it's value is questionable
type Microsoft.FSharp.Core.Option<'t> with
static member OfT (targetOptionType:Type) value =
let someMethod = targetOptionType.GetMethod("Some")
let wrappedValue = someMethod.Invoke(null, [| value |])
wrappedValue
// can't believe there's nothing built-in for this
let (|NullableNull|NullableValue|) (x: _ Nullable) =
if x.HasValue then NullableValue x.Value else NullableNull
// Nullish covers actual null, NullableNull, and None
let (|Nullish|NullableObj|SomeObj|GenericObj|NonNullObj|) (o:obj) =
// consider including empty string in nullish?
Debug.Assert(Nullable<int>() |> box |> isNull)
Debug.Assert(None |> box |> isNull)
match isNull o with
| true -> Nullish
| false ->
let t = o |> getType
// a more direct translation would have been t |> Nullable.GetUnderlyingType|> isNull |> not
match t.IsGenericType with
| false -> NonNullObj
| true ->
let genericType = t.GetGenericTypeDefinition()
if genericType = typedefof<Nullable<_>> then
NullableObj genericType
elif genericType = typedefof<Option<_>> then
SomeObj genericType
else GenericObj genericType
// this may not be even remotely useful, you can just |> Option.ofNullable
module Nullable = //http://bugsquash.blogspot.com/2010/09/nullable-in-f.html also https://gist.github.com/mausch/571158
// [<AutoOpen>]
// module BReusable =
let getValueOrDefault n = match n with NullableValue x -> x | NullableNull -> n.GetValueOrDefault()
//let create x = System.Nullable x (* just use Nullable in and of itself, create is unnecessary. perhaps this is because of F# 4? *)
let getOrDefault v n = match n with NullableValue x -> x | _ -> v
let getOrElse (v: 'a Lazy) (n: 'a Nullable) = match n with NullableValue x -> x | _ -> match v with | Lazy v -> v
let get (x: _ Nullable) = x.Value
let fromOption = Option.toNullable
let toOption = Option.ofNullable
let bind f x =
match x with
| NullableNull -> Nullable()
| NullableValue v -> f v
let hasValue (x: _ Nullable) = x.HasValue
let isNull (x: _ Nullable) = not x.HasValue
let count (x: _ Nullable) = if x.HasValue then 1 else 0
let fold f state x =
match x with
| NullableNull -> state
| NullableValue v -> f state v
let foldBack f x state =
match x with
| NullableNull -> state
| NullableValue _ -> f x state
let exists p x =
match x with
| NullableNull -> false
| NullableValue _ -> p x
let forall p x =
match x with
| NullableNull -> true
| NullableValue _ -> p x
let iter f x =
match x with
| NullableNull -> ()
| NullableValue v -> f v
let map f x =
match x with
| NullableNull -> Nullable()
| NullableValue v -> Nullable(f v)
let toArray x =
match x with
| NullableNull -> [||]
| NullableValue v -> [| v |]
let toList x =
match x with
| NullableNull -> []
| NullableValue v -> [v]
let liftNullable op (a: _ Nullable) (b: _ Nullable) =
if a.HasValue && b.HasValue
then Nullable(op a.Value b.Value)
else Nullable()
let mapBoolOp op a b =
match a,b with
| NullableValue x, NullableValue y -> op x y
| _ -> false
let bindf (n: _ Nullable) f ``default`` = if n.HasValue then f n.Value else ``default``
// things I'm not sure are a good idea but enable things that otherwise might not be possible
// things that create a buttload of complexity one place, to reduce boilerplate or lessen complexity elsewhere
module Ideas =
()
// encapsulate INPC such that, fields can hold INPC values
module Inpc =
open System.ComponentModel
let triggerPropChanged (event:Event<PropertyChangedEventHandler,PropertyChangedEventArgs>) name () =
event.Trigger(null, PropertyChangedEventArgs(name))
// consider adding a param for comparison, so the inpc won't fire if they are equal
// or the parent property exposure could do it?
type InpcWrapper<'T> (fNotifier, defaultValue:'T) =
let mutable field = defaultValue
// consider:
//member x.UnsafeSet v = field <- v
member __.Value
with get() = field
and set v =
field <- v
fNotifier()
// instead of using a parent/base class: use this method!
let createInpc event name defaultValue = InpcWrapper(triggerPropChanged event name, defaultValue)
// sample class for the createInpc method above
type InpcEventWrappedSample () =
let propertyChanged = new Event<_, _>()
let encapsulated = createInpc propertyChanged "Encapsulated" false
member __.Encapsulated
with get() = encapsulated.Value
and set v = if v <> encapsulated.Value then encapsulated.Value <- v
interface INotifyPropertyChanged with
[<CLIEvent>]
member __.PropertyChanged = propertyChanged.Publish
abstract member RaisePropertyChanged : string -> unit
default x.RaisePropertyChanged(propertyName : string) = propertyChanged.Trigger(x, PropertyChangedEventArgs(propertyName))
member x.PropertyChanged = propertyChanged
// is this even remotely useful, when you have quotation helpers above?
module ExpressionHelpers =
open System.Reflection
let maybeUnary (exp:Expression<_>) =
match exp.Body with
| :? UnaryExpression as uExpr -> uExpr.Operand
| x -> x
let inline getMember(expr:Expression<_>) =
if expr = null then raise <| System.ArgumentNullException("expr")
//if expr.Body :? MemberExpression = false then raise <| System.ArgumentException("The body must be a member expression")
let memExpr = maybeUnary expr :?> MemberExpression
if memExpr = null then raise <| System.ArgumentException("The body must be a member expression")
memExpr.Member
let inline GetMemberName(expr:Expression<_>) =
(getMember expr).Name
let inline GetMemberTAct<'t> (expr:Expression<Action<'t>>) =
getMember expr
let inline GetMemberTF(expr:Expression<Func<_>>) =
getMember expr
let inline GetMemberTF2<'t> (expr:Expression<Func<'t,_>>) =
getMember expr
let getMethodOf (expr: Expression<_>) =
let methExpr = expr.Body :?> MethodCallExpression
methExpr.Method
let PropertyInfoOf<'T> (expr : Expression<Func<'T,_>>) =
let mem= getMember expr
mem :?> PropertyInfo
let FieldInfoOf<'T> (expr : Expression<Func<_>>) =
let mem = getMember expr
mem :?> FieldInfo
@tldrlol
Copy link

tldrlol commented Jan 18, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment