From bba7df44dd0a1efc6329996eb06aa97416832db1 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Mon, 3 Aug 2020 23:47:45 -0700 Subject: [PATCH 1/5] Move List object to List module --- src/Fable.Transforms/Fable2Babel.fs | 16 +- src/Fable.Transforms/Replacements.fs | 24 +- src/fable-library/Array.fs | 11 +- src/fable-library/List.fs | 651 +++++++++--------- src/fable-library/Types.ts | 238 +++---- .../test/bench-compiler/package.json | 2 + tests/Main/ComparisonTests.fs | 4 + 7 files changed, 490 insertions(+), 456 deletions(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 796853a0cd..094ee178cb 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -373,7 +373,7 @@ module Util = makeNativeTypeAnnotation com ctx [genArg] "Array" and makeListTypeAnnotation com ctx genArg = - makeImportTypeAnnotation com ctx [genArg] "Types" "List" + makeImportTypeAnnotation com ctx [genArg] "List" "List" and makeUnionTypeAnnotation com ctx genArgs = List.map (typeAnnotation com ctx) genArgs @@ -809,12 +809,12 @@ module Util = // Optimization for bundle size: compile list literals as List.ofArray | Replacements.ListLiteral(exprs, t) -> [|List.rev exprs |> makeArray com ctx|] - |> coreLibCall com ctx r "Types" "newList" + |> coreLibCall com ctx r "List" "newList" | Fable.NewList (headAndTail, _) -> match headAndTail with - | None -> coreLibCall com ctx r "Types" "newList" [||] + | None -> coreLibCall com ctx r "List" "newList" [||] | Some(TransformExpr com ctx head, TransformExpr com ctx tail) -> - coreLibCall com ctx r "Types" "cons" [|head; tail|] + coreLibCall com ctx r "List" "cons" [|head; tail|] | Fable.NewOption (value, t) -> match value with | Some (TransformExpr com ctx e) -> @@ -1054,8 +1054,8 @@ module Util = let expr = com.TransformAsExpr(ctx, fableExpr) match getKind with | Fable.ExprGet(TransformExpr com ctx prop) -> getExpr range expr prop - | Fable.ListHead -> get range expr "Head" - | Fable.ListTail -> get range expr "Tail" + | Fable.ListHead -> coreLibCall com ctx range "List" "head" [|expr|] + | Fable.ListTail -> coreLibCall com ctx range "List" "tail" [|expr|] | Fable.FieldGet(fieldName,_,_) -> let expr = match fableExpr with @@ -1151,7 +1151,7 @@ module Util = | Fable.FunctionType _ -> jsTypeof "function" expr | Fable.Array _ | Fable.Tuple _ -> coreLibCall com ctx None "Util" "isArrayLike" [|com.TransformAsExpr(ctx, expr)|] - | Fable.List _ -> jsInstanceof (coreValue com ctx "Types" "List") expr + | Fable.List _ -> jsInstanceof (coreValue com ctx "List" "List") expr | Replacements.Builtin kind -> match kind with | Replacements.BclGuid -> jsTypeof "string" expr @@ -1212,7 +1212,7 @@ module Util = let op = if nonEmpty then BinaryUnequal else BinaryEqual upcast BinaryExpression(op, com.TransformAsExpr(ctx, expr), NullLiteral(), ?loc=range) | Fable.ListTest nonEmpty -> - let expr = get range (com.TransformAsExpr(ctx, expr)) "IsEmpty" + let expr = coreLibCall com ctx range "List" "isEmpty" [|com.TransformAsExpr(ctx, expr)|] if nonEmpty then upcast UnaryExpression(UnaryNot, expr, ?loc=range) else expr | Fable.UnionCaseTest(uci, ent) -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 652eb41bf1..3819f885ac 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -717,6 +717,8 @@ let identityHash r (arg: Expr) = Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r) | DeclaredType(ent,_) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType -> Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r) + | DeclaredType(ent,_) -> + Helper.InstanceCall(arg, "GetHashCode", Number Int32, [], ?loc=r) | _ -> Helper.CoreCall("Util", "identityHash", Number Int32, [arg], ?loc=r) @@ -1838,30 +1840,20 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex Helper.CoreCall("Array", meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = + let meth = Naming.removeGetSetPrefix i.CompiledName |> Naming.lowerFirst match i.CompiledName, thisArg, args with - // Use methods for Head and Tail (instead of Get(ListHead) for example) to check for empty lists - | ("get_Head" | "get_Tail" | "get_Length" | "get_IsEmpty"), Some callee, _ -> - let meth = Naming.removeGetSetPrefix i.CompiledName - get r t callee meth |> Some - | "get_Item", Some callee, _ -> - let meth = Naming.removeGetSetPrefix i.CompiledName - Helper.InstanceCall(callee, meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some - | "GetSlice", Some x, _ -> - let meth = Naming.lowerFirst i.CompiledName - let args = match args with [ExprType Unit] -> [x] | args -> args @ [x] + | ("get_Head" | "get_Tail" | "get_Length" | "get_IsEmpty"), Some x, _ -> + Helper.CoreCall("List", meth, t, [x], i.SignatureArgTypes, ?loc=r) |> Some + | ("get_Item" | "GetSlice"), Some x, _ -> + Helper.CoreCall("List", meth, t, args @ [x], i.SignatureArgTypes, ?loc=r) |> Some + | ("get_Empty" | "Cons"), None, _ -> Helper.CoreCall("List", meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some - | "get_Empty", None, _ -> NewList(None, (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some - | "Cons", None, [h;t] -> NewList(Some(h,t), (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some | ("GetHashCode" | "Equals" | "CompareTo"), Some callee, _ -> Helper.InstanceCall(callee, i.CompiledName, t, args, i.SignatureArgTypes, ?loc=r) |> Some | _ -> None let listModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName, args with - | "IsEmpty", [x] -> Test(x, ListTest false, r) |> Some - | "Empty", _ -> NewList(None, (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some - | "Singleton", [x] -> - NewList(Some(x, Value(NewList(None, t), None)), (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some // Use a cast to give it better chances of optimization (e.g. converting list // literals to arrays) after the beta reduction pass | "ToSeq", [x] -> toSeq t x |> Some diff --git a/src/fable-library/Array.fs b/src/fable-library/Array.fs index 5710ffd995..8909faf584 100644 --- a/src/fable-library/Array.fs +++ b/src/fable-library/Array.fs @@ -123,7 +123,6 @@ module Helpers = open Helpers let private indexNotFoundMsg = "An index satisfying the predicate was not found in the collection." -let inline indexNotFound() = failwith indexNotFoundMsg // Pay attention when benchmarking to append and filter functions below // if implementing via native JS array .concat() and .filter() do not fall behind due to js-native transitions. @@ -478,7 +477,7 @@ let partition (f: 'T -> bool) (source: 'T[]) ([] cons: IArrayCons<'T>) = let find (predicate: 'T -> bool) (array: 'T[]): 'T = match findImpl predicate array with | Some res -> res - | None -> indexNotFound() + | None -> failwith indexNotFoundMsg let tryFind (predicate: 'T -> bool) (array: 'T[]): 'T option = findImpl predicate array @@ -486,7 +485,7 @@ let tryFind (predicate: 'T -> bool) (array: 'T[]): 'T option = let findIndex (predicate: 'T -> bool) (array: 'T[]): int = match findIndexImpl predicate array with | index when index > -1 -> index - | _ -> indexNotFound() + | _ -> failwith indexNotFoundMsg let tryFindIndex (predicate: 'T -> bool) (array: 'T[]): int option = match findIndexImpl predicate array with @@ -496,7 +495,7 @@ let tryFindIndex (predicate: 'T -> bool) (array: 'T[]): int option = let pick chooser (array: _[]) = let rec loop i = if i >= array.Length then - indexNotFound() + failwith indexNotFoundMsg else match chooser array.[i] with | None -> loop(i+1) @@ -513,7 +512,7 @@ let tryPick chooser (array: _[]) = let findBack predicate (array: _[]) = let rec loop i = - if i < 0 then indexNotFound() + if i < 0 then failwith indexNotFoundMsg elif predicate array.[i] then array.[i] else loop (i - 1) loop (array.Length - 1) @@ -534,7 +533,7 @@ let findLastIndex predicate (array: _[]) = let findIndexBack predicate (array: _[]) = let rec loop i = - if i < 0 then indexNotFound() + if i < 0 then failwith indexNotFoundMsg elif predicate array.[i] then i else loop (i - 1) loop (array.Length - 1) diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index c266ca9be0..c84aad202b 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -3,156 +3,237 @@ module List // Disables warn:1204 raised by use of LanguagePrimitives.ErrorStrings.* #nowarn "1204" -open System.Collections.Generic open Fable.Core -let head = function - | x::_ -> x - | _ -> failwith "List was empty" +let msgListWasEmpty = "List was empty" +let msgListNoMatch = "List did not contain any matching elements" + +type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = + let mutable hashCode = None + + static member Empty = new List<'T>(0, ResizeArray<'T>()) + static member internal Cons (x: 'T, xs: 'T list) = xs.Add(x) + + member inline internal _.Add(x: 'T) = + let values = + if Count = Values.Count + then Values + else Values.GetRange(0, Count) + values.Add(x) + new List<'T>(values.Count, values) + + member inline _.IsEmpty = Count <= 0 + member inline _.Length = Count + + member _.Head = + if Count > 0 + then Values.[Count - 1] + else failwith msgListWasEmpty + + member _.Tail = + if Count > 0 + then new List<'T>(Count - 1, Values) + else failwith msgListWasEmpty + + member inline _.Item with get(index) = + Values.[Count - 1 - index] + + override xs.ToString() = + "[" + System.String.Join("; ", xs) + "]" + + override xs.Equals(other: obj) = + let ys = other :?> 'T list + if xs.Length <> ys.Length then false + elif xs.GetHashCode() <> ys.GetHashCode() then false + else Seq.forall2 (Unchecked.equals) xs ys + // else (xs :> System.IComparable).CompareTo(other) = 0 + + override xs.GetHashCode() = + match hashCode with + | Some h -> h + | None -> + let inline combineHash i x y = (x <<< 1) + y + 631 * i + let len = min (xs.Length - 1) 18 // limit the hash count + let mutable h = 0 + for i = 0 to len do + h <- combineHash i h (hash xs.[i]) + hashCode <- Some h + h + + interface System.IComparable with + member xs.CompareTo(other: obj) = + Seq.compareWith compare xs (other :?> 'T list) + // List.CompareWith compare xs (other :?> 'T list) + + interface System.Collections.Generic.IEnumerable<'T> with + member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = + let elems = seq { for i=xs.Length - 1 downto 0 do yield Values.[i] } + elems.GetEnumerator() + + // new ListEnumerator<'T>(xs) :> System.Collections.Generic.IEnumerator<'T> + + // let mutable i = Count + // { + // new System.Collections.Generic.IEnumerator<'T> with + // member _.Current = Values.[i] + // interface System.Collections.IEnumerator with + // member _.Current: obj = box (Values.[i]) + // member _.MoveNext() = i <- i - 1; i >= 0 + // member _.Reset() = i <- Count + // interface System.IDisposable with + // member _.Dispose(): unit = () + // } + + interface System.Collections.IEnumerable with + member xs.GetEnumerator(): System.Collections.IEnumerator = + ((xs :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() :> System.Collections.IEnumerator) + + // static member internal CompareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = + // if obj.ReferenceEquals(xs, ys) + // then 0 + // else + // if xs.IsEmpty then + // if ys.IsEmpty then 0 else -1 + // elif ys.IsEmpty then 1 + // else + // let mutable i = 0 + // let mutable result = 0 + // if xs.Length > ys.Length then 1 + // elif xs.Length < ys.Length then -1 + // else + // while i < xs.Length && result = 0 do + // result <- comparer xs.[i] ys.[i] + // i <- i + 1 + // result + +// and ListEnumerator<'T when 'T: comparison>(xs: List<'T>) = +// let mutable i = -1 +// interface System.Collections.Generic.IEnumerator<'T> with +// member __.Current = xs.[i] +// interface System.Collections.IEnumerator with +// member __.Current = box (xs.[i]) +// member __.MoveNext() = i <- i + 1; i < xs.Length +// member __.Reset() = i <- -1 +// interface System.IDisposable with +// member __.Dispose() = () + +and 'T list when 'T: comparison = List<'T> + +let newList (values: ResizeArray<'T>) = new List<'T>(values.Count, values) + +let empty () = List.Empty + +let cons (x: 'T) (xs: 'T list) = List.Cons (x, xs) + +let singleton x = cons x List.Empty + +let isEmpty (xs: 'T list) = xs.IsEmpty + +let length (xs: 'T list) = xs.Length + +let head (xs: 'T list) = xs.Head + +let tryHead (xs: 'T list) = + if xs.Length > 0 + then Some xs.[0] + else None + +let tail (xs: 'T list) = xs.Tail + +let last (xs: 'T list) = + if xs.Length > 0 + then xs.[xs.Length - 1] + else failwith msgListWasEmpty + +let tryLast (xs: 'T list) = + if xs.Length > 0 + then Some xs.[xs.Length - 1] + else None -let tryHead = function - | x::_ -> Some x - | _ -> None - -let tail = function - | _::xs -> xs - | _ -> failwith "List was empty" +let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = + Seq.compareWith comparer xs ys + //List.CompareWith comparer xs ys -let rec last = function - | [] -> failwith "List was empty" - | [x] -> x - | _::xs -> last xs +let fold (folder: 'acc -> 'T -> 'acc) (state: 'acc) (xs: 'T list) = + let mutable acc = state + for i = 0 to xs.Length - 1 do + acc <- folder acc xs.[i] + acc -let rec tryLast = function - | [] -> None - | [x] -> Some x - | _::xs -> tryLast xs +let foldBack (folder: 'T -> 'acc -> 'acc) (xs: 'T list) (state: 'acc) = + let mutable acc = state + for i = xs.Length - 1 downto 0 do + acc <- folder xs.[i] acc + acc -let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = - if obj.ReferenceEquals(xs, ys) - then 0 - else - let rec loop xs ys = - match xs, ys with - | [], [] -> 0 - | [], _ -> -1 - | _, [] -> 1 - | x::xs, y::ys -> - match comparer x y with - | 0 -> loop xs ys - | res -> res - loop xs ys - -let rec foldIndexedAux f i acc = function - | [] -> acc - | x::xs -> foldIndexedAux f (i+1) (f i acc x) xs - -let foldIndexed<'a,'acc> f (state: 'acc) (xs: 'a list) = - foldIndexedAux f 0 state xs - -let rec fold<'a,'acc> f (state: 'acc) (xs: 'a list) = - match xs with - | [] -> state - | h::t -> fold f (f state h) t - -let reverse xs = - fold (fun acc x -> x::acc) [] xs - -let foldBack<'a,'acc> f (xs: 'a list) (state: 'acc) = - fold (fun acc x -> f x acc) state (reverse xs) +let reverse (xs: 'a list) = + fold (fun acc x -> cons x acc) List.Empty xs let toSeq (xs: 'a list): 'a seq = Seq.map id xs let ofSeq (xs: 'a seq): 'a list = - Seq.fold (fun acc x -> x::acc) [] xs + Seq.fold (fun acc x -> cons x acc) List.Empty xs |> reverse let concat (lists: seq<'a list>) = - Seq.fold (fold (fun acc x -> x::acc)) [] lists + Seq.fold (fold (fun acc x -> cons x acc)) List.Empty lists |> reverse -let rec foldIndexed2Aux f i acc bs cs = - match bs, cs with - | [], [] -> acc - | x::xs, y::ys -> foldIndexed2Aux f (i+1) (f i acc x y) xs ys - | _ -> invalidOp "Lists had different lengths" - -let foldIndexed2<'a, 'b, 'acc> f (state: 'acc) (xs: 'a list) (ys: 'b list) = - foldIndexed2Aux f 0 state xs ys - -let fold2<'a, 'b, 'acc> f (state: 'acc) (xs: 'a list) (ys: 'b list) = +let fold2 f (state: 'acc) (xs: 'a list) (ys: 'b list) = Seq.fold2 f state xs ys -let foldBack2<'a, 'b, 'acc> f (xs: 'a list) (ys: 'b list) (state: 'acc) = +let foldBack2 f (xs: 'a list) (ys: 'b list) (state: 'acc) = Seq.foldBack2 f xs ys state -let unfold f state = - let rec unfoldInner acc state = - match f state with - | None -> reverse acc - | Some (x,state) -> unfoldInner (x::acc) state - unfoldInner [] state - -let rec foldIndexed3Aux f i acc bs cs ds = - match bs, cs, ds with - | [], [], [] -> acc - | x::xs, y::ys, z::zs -> foldIndexed3Aux f (i+1) (f i acc x y z) xs ys zs - | _ -> invalidOp "Lists had different lengths" - -let foldIndexed3<'a, 'b, 'c, 'acc> f (seed: 'acc) (xs: 'a list) (ys: 'b list) (zs: 'c list) = - foldIndexed3Aux f 0 seed xs ys zs - -let fold3<'a, 'b, 'c, 'acc> f (state: 'acc) (xs: 'a list) (ys: 'b list) (zs: 'c list) = - foldIndexed3 (fun _ acc x y z -> f acc x y z) state xs ys zs +let unfold (gen: 'acc -> ('T * 'acc) option) (state: 'acc) = + let rec loop st acc = + match gen st with + | None -> acc + | Some (x, st) -> loop st (cons x acc) + loop state List.Empty + |> reverse -let scan<'a, 'acc> f (state: 'acc) (xs: 'a list) = +let scan f (state: 'acc) (xs: 'a list) = Seq.scan f state xs |> ofSeq -let scanBack<'a, 'acc> f (xs: 'a list) (state: 'acc) = +let scanBack f (xs: 'a list) (state: 'acc) = Seq.scanBack f xs state |> ofSeq -let length xs = - fold (fun acc _ -> acc + 1) 0 xs - -let append xs ys = - fold (fun acc x -> x::acc) ys (reverse xs) +let append (xs: 'a list) (ys: 'a list) = + fold (fun acc x -> cons x acc) ys (reverse xs) let collect (f: 'a -> 'b list) (xs: 'a list) = Seq.collect f xs |> ofSeq -let map f xs = - fold (fun acc x -> f x::acc) [] xs +let mapIndexed (f: int -> 'a -> 'b) (xs: 'a list) = + let rec loop i acc = + if i < xs.Length + then loop (i + 1) (cons (f i xs.[i]) acc) + else acc + loop 0 List.Empty |> reverse -let mapIndexed f xs = - foldIndexed (fun i acc x -> f i x::acc) [] xs - |> reverse +let map (f: 'a -> 'b) (xs: 'a list) = + mapIndexed (fun i x -> f x) xs -let indexed xs = - mapIndexed (fun i x -> (i,x)) xs +let indexed (xs: 'a list) = + mapIndexed (fun i x -> (i, x)) xs let map2 f xs ys = - fold2 (fun acc x y -> f x y::acc) [] xs ys - |> reverse + Seq.map2 f xs ys |> ofSeq let mapIndexed2 f xs ys = - foldIndexed2 (fun i acc x y -> f i x y:: acc) [] xs ys - |> reverse + Seq.mapi2 f xs ys |> ofSeq let map3 f xs ys zs = - fold3 (fun acc x y z -> f x y z::acc) [] xs ys zs - |> reverse - -let mapIndexed3 f xs ys zs = - foldIndexed3 (fun i acc x y z -> f i x y z:: acc) [] xs ys zs - |> reverse + Seq.map3 f xs ys zs |> ofSeq let mapFold (f: 'S -> 'T -> 'R * 'S) s xs = - let foldFn (nxs, fs) x = + let folder (nxs, fs) x = let nx, fs = f fs x - nx::nxs, fs - let nxs, s = fold foldFn ([], s) xs + cons nx nxs, fs + let nxs, s = fold folder (List.Empty, s) xs reverse nxs, s let mapFoldBack (f: 'T -> 'S -> 'R * 'S) xs s = @@ -165,132 +246,138 @@ let iterate2 f xs ys = fold2 (fun () x y -> f x y) () xs ys let iterateIndexed f xs = - foldIndexed (fun i () x -> f i x) () xs + fold (fun i x -> f i x; i + 1) 0 xs |> ignore let iterateIndexed2 f xs ys = - foldIndexed2 (fun i () x y -> f i x y) () xs ys + fold2 (fun i x y -> f i x y; i + 1) 0 xs ys |> ignore -let ofArray (xs: IList<'T>) = - // Array.foldBack (fun x acc -> x::acc) xs [] - let mutable res = [] +let ofArray (xs: System.Collections.Generic.IList<'T>) = + let mutable res = List.Empty for i = xs.Count - 1 downto 0 do - res <- xs.[i]::res + res <- cons xs.[i] res res -let empty<'a> : 'a list = [] - -let isEmpty = function - | [] -> true - | _ -> false - -let rec tryPickIndexedAux f i = function - | [] -> None - | x::xs -> - let result = f i x - match result with - | Some _ -> result - | None -> tryPickIndexedAux f (i+1) xs - -let tryPickIndexed f xs = - tryPickIndexedAux f 0 xs +let tryPickIndexed (f: int -> 'a -> 'b option) (xs: 'a list) = + let rec loop i = + let res = f i xs.[i] + match res with + | Some _ -> res + | None -> if i < xs.Length - 1 then loop (i + 1) else None + if xs.Length > 0 then loop 0 else None + +let tryPickIndexedBack (f: int -> 'a -> 'b option) (xs: 'a list) = + let rec loop i = + let res = f i xs.[i] + match res with + | Some _ -> res + | None -> if i > 0 then loop (i - 1) else None + if xs.Length > 0 then loop (xs.Length - 1) else None let tryPick f xs = tryPickIndexed (fun _ x -> f x) xs let pick f xs = match tryPick f xs with - | None -> invalidOp "List did not contain any matching elements" + | None -> invalidOp msgListNoMatch | Some x -> x let tryFindIndexed f xs = tryPickIndexed (fun i x -> if f i x then Some x else None) xs -let tryFind f xs = - tryPickIndexed (fun _ x -> if f x then Some x else None) xs +let tryFindIndexedBack f xs = + tryPickIndexedBack (fun i x -> if f i x then Some x else None) xs let findIndexed f xs = match tryFindIndexed f xs with - | None -> invalidOp "List did not contain any matching elements" + | None -> invalidOp msgListNoMatch + | Some x -> x + +let findIndexedBack f xs = + match tryFindIndexedBack f xs with + | None -> invalidOp msgListNoMatch | Some x -> x let find f xs = findIndexed (fun _ x -> f x) xs let findBack f xs = - xs |> reverse |> find f + findIndexedBack (fun _ x -> f x) xs + +let tryFind f xs = + tryPickIndexed (fun _ x -> if f x then Some x else None) xs let tryFindBack f xs = - xs |> reverse |> tryFind f + tryPickIndexedBack (fun _ x -> if f x then Some x else None) xs let tryFindIndex f xs: int option = tryPickIndexed (fun i x -> if f x then Some i else None) xs let tryFindIndexBack f xs: int option = - List.toArray xs - |> Array.tryFindIndexBack f + tryPickIndexedBack (fun i x -> if f x then Some i else None) xs let findIndex f xs: int = match tryFindIndex f xs with - | None -> invalidOp "List did not contain any matching elements" + | None -> invalidOp msgListNoMatch | Some x -> x let findIndexBack f xs: int = - List.toArray xs - |> Array.findIndexBack f + match tryFindIndexBack f xs with + | None -> invalidOp msgListNoMatch + | Some x -> x -let item n xs = - findIndexed (fun i _ -> n = i) xs +let item n (xs: 'a list) = + if n >= 0 && n < xs.Length + then xs.[n] + else failwith "Index out of range" -let tryItem n xs = - tryFindIndexed (fun i _ -> n = i) xs +let tryItem n (xs: 'a list) = + if n >= 0 && n < xs.Length + then Some xs.[n] + else None let filter f xs = fold (fun acc x -> - if f x then x::acc - else acc) [] xs |> reverse + if f x + then cons x acc + else acc) List.Empty xs + |> reverse let partition f xs = fold (fun (lacc, racc) x -> - if f x then x::lacc, racc - else lacc,x::racc) ([],[]) (reverse xs) + if f x then cons x lacc, racc + else lacc, cons x racc) (List.Empty, List.Empty) (reverse xs) let choose f xs = fold (fun acc x -> match f x with - | Some y -> y:: acc - | None -> acc) [] xs |> reverse - -let contains<'T> (value: 'T) (list: 'T list) ([] eq: IEqualityComparer<'T>) = - let rec loop xs = - match xs with - | [] -> false - | v::rest -> - if eq.Equals (value, v) - then true - else loop rest - loop list - -let except (itemsToExclude: seq<'t>) (array: 't list) ([] eq: IEqualityComparer<'t>): 't list = - if isEmpty array then array + | Some y -> cons y acc + | None -> acc) List.Empty xs |> reverse + +let contains (value: 'T) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = + tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome + +let except (itemsToExclude: seq<'t>) (xs: 't list) ([] eq: System.Collections.Generic.IEqualityComparer<'t>): 't list = + if isEmpty xs then xs else - let cached = HashSet(itemsToExclude, eq) - array |> filter cached.Add + let cached = System.Collections.Generic.HashSet(itemsToExclude, eq) + xs |> filter cached.Add let initialize n f = - let mutable xs = [] - for i = 0 to n-1 do xs <- (f i)::xs - reverse xs + let mutable res = List.Empty + for i = 0 to n - 1 do + res <- cons (f i) res + res |> reverse let replicate n x = initialize n (fun _ -> x) -let reduce f = function - | [] -> invalidOp "List was empty" - | h::t -> fold f h t +let reduce f (xs: 't list) = + if isEmpty xs then invalidOp msgListWasEmpty + else fold f (head xs) (tail xs) -let reduceBack f = function - | [] -> invalidOp "List was empty" - | h::t -> foldBack f t h +let reduceBack f (xs: 't list) = + if isEmpty xs then invalidOp msgListWasEmpty + else foldBack f (tail xs) (head xs) let forAll f xs = fold (fun acc x -> acc && f x) true xs @@ -298,21 +385,20 @@ let forAll f xs = let forAll2 f xs ys = fold2 (fun acc x y -> acc && f x y) true xs ys -let rec exists f = function - | [] -> false - | x::xs -> f x || exists f xs +let exists f xs = + tryFindIndex f xs |> Option.isSome -let rec exists2 f bs cs = - match bs, cs with - | [], [] -> false - | x::xs, y::ys -> f x y || exists2 f xs ys +let rec exists2 f xs ys = + match length xs, length ys with + | 0, 0 -> false + | x, y when x = y -> f (head xs) (head ys) || exists2 f (tail xs) (tail ys) | _ -> invalidOp "Lists had different lengths" let unzip xs = - foldBack (fun (x, y) (lacc, racc) -> x::lacc, y::racc) xs ([],[]) + foldBack (fun (x, y) (lacc, racc) -> cons x lacc, cons y racc) xs (List.Empty, List.Empty) let unzip3 xs = - foldBack (fun (x, y, z) (lacc, macc, racc) -> x::lacc, y::macc, z::racc) xs ([],[],[]) + foldBack (fun (x, y, z) (lacc, macc, racc) -> cons x lacc, cons y macc, cons z racc) xs (List.Empty, List.Empty, List.Empty) let zip xs ys = map2 (fun x y -> x, y) xs ys @@ -320,20 +406,22 @@ let zip xs ys = let zip3 xs ys zs = map3 (fun x y z -> x, y, z) xs ys zs -let sort (xs: 'T list) ([] comparer: IComparer<'T>): 'T list = - Array.sortInPlaceWith (fun x y -> comparer.Compare(x, y)) (List.toArray xs) |> ofArray +let sortWith (comparison: 'T -> 'T -> int) (xs: 'T list): 'T list = + let values = ResizeArray(xs) + values.Sort(System.Comparison<_>(comparison)) + values |> ofSeq -let sortBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a list = - Array.sortInPlaceWith (fun x y -> comparer.Compare(projection x, projection y)) (List.toArray xs) |> ofArray +let sort (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = + sortWith (fun x y -> comparer.Compare(x, y)) xs -let sortDescending (xs: 'T list) ([] comparer: IComparer<'T>): 'T list = - Array.sortInPlaceWith (fun x y -> comparer.Compare(x, y) * -1) (List.toArray xs) |> ofArray +let sortBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = + sortWith (fun x y -> comparer.Compare(projection x, projection y)) xs -let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a list = - Array.sortInPlaceWith (fun x y -> comparer.Compare(projection x, projection y) * -1) (List.toArray xs) |> ofArray +let sortDescending (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = + sortWith (fun x y -> comparer.Compare(x, y) * -1) xs -let sortWith (comparer: 'T -> 'T -> int) (xs: 'T list): 'T list = - Array.sortInPlaceWith comparer (List.toArray xs) |> ofArray +let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = + sortWith (fun x y -> comparer.Compare(projection x, projection y) * -1) xs let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = fold (fun acc x -> adder.Add(acc, x)) (adder.GetZero()) xs @@ -341,16 +429,16 @@ let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = let sumBy (f: 'T -> 'T2) (xs: 'T list) ([] adder: IGenericAdder<'T2>): 'T2 = fold (fun acc x -> adder.Add(acc, f x)) (adder.GetZero()) xs -let maxBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a = +let maxBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then y else x) xs -let max (li:'a list) ([] comparer: IComparer<'a>): 'a = +let max (li:'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = reduce (fun x y -> if comparer.Compare(y, x) > 0 then y else x) li -let minBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a = +let minBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then x else y) xs -let min (xs: 'a list) ([] comparer: IComparer<'a>): 'a = +let min (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = reduce (fun x y -> if comparer.Compare(y, x) > 0 then x else y) xs let average (xs: 'T list) ([] averager: IGenericAverager<'T>): 'T = @@ -361,124 +449,78 @@ let averageBy (f: 'T -> 'T2) (xs: 'T list) ([] averager: IGenericAverage let total = fold (fun acc x -> averager.Add(acc, f x)) (averager.GetZero()) xs averager.DivideByInt(total, length xs) -let permute f xs = - xs - |> List.toArray +let permute f (xs: 'T list) = + Array.ofSeq xs Array.DynamicArrayCons |> Array.permute f |> ofArray let chunkBySize (chunkSize: int) (xs: 'T list): 'T list list = - xs - |> List.toArray + Array.ofSeq xs Array.DynamicArrayCons |> Array.chunkBySize chunkSize |> ofArray |> map ofArray -let skip i xs = - let rec skipInner i xs = - match i, xs with - | 0, _ -> xs - | _, [] -> failwith "The input sequence has an insufficient number of elements." - | _, _::xs -> skipInner (i - 1) xs - match i, xs with - | i, _ when i < 0 -> failwith "The input must be non-negative." - | 0, _ -> xs - | 1, _::xs -> xs - | i, xs -> skipInner i xs - -let rec skipWhile predicate xs = - match xs with - | h::t when predicate h -> skipWhile predicate t - | _ -> xs - -// TODO: Is there a more efficient algorithm? -let rec takeSplitAux error i acc xs = - match i, xs with - | 0, _ -> reverse acc, xs - | _, [] -> - if error then - failwith "The input sequence has an insufficient number of elements." - else - reverse acc, xs - | _, x::xs -> takeSplitAux error (i - 1) (x::acc) xs +let skip i (xs: 'T list) = + Seq.skip i xs |> ofSeq -let take i xs = - match i, xs with - | i, _ when i < 0 -> failwith "The input must be non-negative." - | 0, _ -> [] - | 1, x::_ -> [x] - | i, xs -> takeSplitAux true i [] xs |> fst - -let rec takeWhile predicate (xs: 'T list) = - match xs with - | [] -> xs - | x::([] as nil) -> if predicate x then xs else nil - | x::xs -> - if not (predicate x) then [] - else x::(takeWhile predicate xs) +let skipWhile predicate (xs: 'T list) = + Seq.skipWhile predicate xs |> ofSeq -let truncate i xs = - match i, xs with - | i, _ when i < 0 -> failwith "The input must be non-negative." - | 0, _ -> [] - | 1, x::_ -> [x] - | i, xs -> takeSplitAux false i [] xs |> fst +let take i xs = + Seq.take i xs |> ofSeq -let splitAt i xs = - match i, xs with - | i, _ when i < 0 -> failwith "The input must be non-negative." - | 0, _ -> [],xs - | 1, x::xs -> [x],xs - | i, xs -> takeSplitAux true i [] xs +let takeWhile predicate (xs: 'T list) = + Seq.takeWhile predicate xs |> ofSeq -let outOfRange() = failwith "Index out of range" +let truncate i xs = + Seq.truncate i xs |> ofSeq let getSlice (lower: int option) (upper: int option) (xs: 'T list) = let lower = defaultArg lower 0 - let hasUpper = Option.isSome upper - if lower < 0 then outOfRange() - elif hasUpper && upper.Value < lower then [] + let upper = defaultArg upper (xs.Length - 1) + if lower < 0 || upper >= xs.Length then failwith "Index out of range" + elif upper < lower then List.Empty else - let mutable lastIndex = -1 - let res = - ([], xs) ||> foldIndexed (fun i acc x -> - lastIndex <- i - if lower <= i && (not hasUpper || i <= upper.Value) then x::acc - else acc) - if lower > (lastIndex + 1) || (hasUpper && upper.Value > lastIndex) then outOfRange() - reverse res - -let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([] eq: IEqualityComparer<'Key>) = - let hashSet = HashSet<'Key>(eq) + let values = ResizeArray(upper - lower + 1) + for i = lower to upper do values.Add(xs.[i]) + values |> ofSeq + +let splitAt i (xs: 'T list) = + if i < 0 then invalidArg "index" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString + if i > xs.Length then invalidArg "index" "The input sequence has an insufficient number of elements." + take i xs, skip i xs + +let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let hashSet = System.Collections.Generic.HashSet<'Key>(eq) xs |> filter (projection >> hashSet.Add) -let distinct (xs: 'T list) ([] eq: IEqualityComparer<'T>) = +let distinct (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = distinctBy id xs eq let exactlyOne (xs: 'T list) = - match xs with - | [] -> invalidArg "list" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString - | [x] -> x - | x1::x2::xs -> invalidArg "list" "Input list too long" - -let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityComparer<'Key>): ('Key * 'T list) list = - let dict = Dictionary<'Key, 'T list>(eq) - let mutable keys = [] + match xs.Length with + | 1 -> head xs + | 0 -> invalidArg "list" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + | _ -> invalidArg "list" "Input list too long" + +let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>): ('Key * 'T list) list = + let dict = System.Collections.Generic.Dictionary<'Key, 'T list>(eq) + let mutable keys = List.Empty xs |> iterate (fun v -> let key = projection v match dict.TryGetValue(key) with | true, prev -> - dict.[key] <- v::prev + dict.[key] <- cons v prev | false, _ -> - dict.Add(key, [v]) - keys <- key::keys ) - let mutable result = [] - keys |> iterate (fun key -> result <- (key, reverse dict.[key]) :: result) + dict.Add(key, cons v List.Empty) + keys <- cons key keys ) + let mutable result = List.Empty + keys |> iterate (fun key -> result <- cons (key, reverse dict.[key]) result) result -let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityComparer<'Key>) = - let dict = Dictionary<'Key, int>(eq) - let mutable keys = [] +let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let dict = System.Collections.Generic.Dictionary<'Key, int>(eq) + let mutable keys = List.Empty xs |> iterate (fun v -> let key = projection v match dict.TryGetValue(key) with @@ -486,29 +528,24 @@ let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityCompa dict.[key] <- prev + 1 | false, _ -> dict.[key] <- 1 - keys <- key::keys ) - let mutable result = [] - keys |> iterate (fun key -> result <- (key, dict.[key]) :: result) + keys <- cons key keys ) + let mutable result = List.Empty + keys |> iterate (fun key -> result <- cons (key, dict.[key]) result) result -let where predicate source = - filter predicate source +let where predicate (xs: 'T list) = + filter predicate xs -let pairwise source = - Seq.pairwise source - |> ofSeq +let pairwise (xs: 'T list) = + Seq.pairwise xs |> ofSeq -let windowed (windowSize: int) (source: 'T list): 'T list list = - if windowSize <= 0 then - failwith "windowSize must be positive" - let mutable res = [] - for i = length source downto windowSize do - res <- (getSlice (Some(i-windowSize)) (Some(i-1)) source) :: res - res +let windowed (windowSize: int) (xs: 'T list): 'T list list = + Seq.windowed windowSize xs + |> ofSeq + |> map ofArray -let splitInto (chunks: int) (source: 'T list): 'T list list = - source - |> List.toArray +let splitInto (chunks: int) (xs: 'T list): 'T list list = + Array.ofSeq xs Array.DynamicArrayCons |> Array.splitInto chunks |> ofArray |> map ofArray diff --git a/src/fable-library/Types.ts b/src/fable-library/Types.ts index cfb4d4bcea..ae7035b2d6 100644 --- a/src/fable-library/Types.ts +++ b/src/fable-library/Types.ts @@ -47,139 +47,139 @@ export class SystemObject implements IEquatable { } } -function compareList(self: List, other: List) { - if (self === other) { - return 0; - } else { - if (other == null) { - return -1; - } - const selfLen = self.Length; - const otherLen = other.Length; - const minLen = Math.min(selfLen, otherLen); - for (let i = 0; i < minLen; i++) { - const res = compare(self.Item(i), other.Item(i)); - if (res !== 0) { return res; } - } - return selfLen > otherLen ? 1 : (selfLen < otherLen ? -1 : 0); - } -} +// function compareList(self: List, other: List) { +// if (self === other) { +// return 0; +// } else { +// if (other == null) { +// return -1; +// } +// const selfLen = self.Length; +// const otherLen = other.Length; +// const minLen = Math.min(selfLen, otherLen); +// for (let i = 0; i < minLen; i++) { +// const res = compare(self.Item(i), other.Item(i)); +// if (res !== 0) { return res; } +// } +// return selfLen > otherLen ? 1 : (selfLen < otherLen ? -1 : 0); +// } +// } -export function newList(vals: T[]): List { - return new List(vals); -} +// export function newList(vals: T[]): List { +// return new List(vals); +// } -export function cons(head: T, tail: List): List { - // If this points to the last index of the stack, push the new value into it. - // Otherwise, this becomes an "actual" tail. - if (tail.vals.length === tail.idx + 1) { - tail.vals.push(head); - return new List(tail.vals, tail.tail); - } else { - return new List([head], tail); - } -} +// export function cons(head: T, tail: List): List { +// // If this points to the last index of the stack, push the new value into it. +// // Otherwise, this becomes an "actual" tail. +// if (tail.vals.length === tail.idx + 1) { +// tail.vals.push(head); +// return new List(tail.vals, tail.tail); +// } else { +// return new List([head], tail); +// } +// } -/** - * F# list is represented in runtime by an optimized type that uses a stack (a reverted JS array) - * to store the values, so we can a have a big list represented by a single object (plus the stack). - * It also allows for optimizations in the List module. - */ -export class List implements IEquatable>, IComparable>, Iterable { - public vals: T[]; - public idx: number; - public tail?: List; - - constructor(vals?: T[], tail?: List, idx?: number) { - this.vals = vals ?? []; - this.idx = idx ?? this.vals.length - 1; - this.tail = tail; - } +// /** +// * F# list is represented in runtime by an optimized type that uses a stack (a reverted JS array) +// * to store the values, so we can a have a big list represented by a single object (plus the stack). +// * It also allows for optimizations in the List module. +// */ +// export class List implements IEquatable>, IComparable>, Iterable { +// public vals: T[]; +// public idx: number; +// public tail?: List; + +// constructor(vals?: T[], tail?: List, idx?: number) { +// this.vals = vals ?? []; +// this.idx = idx ?? this.vals.length - 1; +// this.tail = tail; +// } - public Item(i: number): T { - if (i < 0) { - throw new Error("Index out of range"); - } else if (i <= this.idx) { - return this.vals[this.idx - i]; - } else if (this.tail) { - return this.tail.Item(i - this.idx - 1); - } else { - throw new Error("Index out of range"); - } - } +// public Item(i: number): T { +// if (i < 0) { +// throw new Error("Index out of range"); +// } else if (i <= this.idx) { +// return this.vals[this.idx - i]; +// } else if (this.tail) { +// return this.tail.Item(i - this.idx - 1); +// } else { +// throw new Error("Index out of range"); +// } +// } - public get Head(): T { - if (this.idx >= 0) { - return this.vals[this.idx]; - } else if (this.idx < 0 && this.tail) { - return this.tail.Head; - } else { - throw new Error("List was empty"); - } - } +// public get Head(): T { +// if (this.idx >= 0) { +// return this.vals[this.idx]; +// } else if (this.idx < 0 && this.tail) { +// return this.tail.Head; +// } else { +// throw new Error("List was empty"); +// } +// } - public get Tail(): List | undefined { - if (this.idx === 0 && this.tail) { - return this.tail; - } else if (this.idx >= 0) { - return new List(this.vals, this.tail, this.idx - 1); - } else { - return this.tail?.Tail; - } - } +// public get Tail(): List | undefined { +// if (this.idx === 0 && this.tail) { +// return this.tail; +// } else if (this.idx >= 0) { +// return new List(this.vals, this.tail, this.idx - 1); +// } else { +// return this.tail?.Tail; +// } +// } - public get IsEmpty(): boolean { - return this.idx < 0 && (this.tail?.IsEmpty ?? true); - } +// public get IsEmpty(): boolean { +// return this.idx < 0 && (this.tail?.IsEmpty ?? true); +// } - public get Length(): number { - return this.idx + 1 + (this.tail?.Length ?? 0); - } +// public get Length(): number { +// return this.idx + 1 + (this.tail?.Length ?? 0); +// } - public toString() { - return "[" + Array.from(this).join("; ") + "]"; - } +// public toString() { +// return "[" + Array.from(this).join("; ") + "]"; +// } - public toJSON() { - return Array.from(this); - } +// public toJSON() { +// return Array.from(this); +// } - public [Symbol.iterator](): Iterator { - let curIdx = this.idx; - let li: List = this; - return { - next: (): IteratorResult => { - while (curIdx < 0 && li.tail) { - li = li.tail; - curIdx = li.idx; - } - return (curIdx < 0) - ? { done: true, value: undefined } - : { done: false, value: li.vals[curIdx--] }; - } - }; - } +// public [Symbol.iterator](): Iterator { +// let curIdx = this.idx; +// let li: List = this; +// return { +// next: (): IteratorResult => { +// while (curIdx < 0 && li.tail) { +// li = li.tail; +// curIdx = li.idx; +// } +// return (curIdx < 0) +// ? { done: true, value: undefined } +// : { done: false, value: li.vals[curIdx--] }; +// } +// }; +// } - public GetHashCode() { - if (this.idx < 0) { - return 0; - } else { - const hashes: number[] = new Array(this.idx + 1); - for (let i = this.idx; i >= 0; i--) { - hashes[i] = structuralHash(this.vals[i]); - } - return combineHashCodes(hashes); - } - } +// public GetHashCode() { +// if (this.idx < 0) { +// return 0; +// } else { +// const hashes: number[] = new Array(this.idx + 1); +// for (let i = this.idx; i >= 0; i--) { +// hashes[i] = structuralHash(this.vals[i]); +// } +// return combineHashCodes(hashes); +// } +// } - public Equals(other: List): boolean { - return compareList(this, other) === 0; - } +// public Equals(other: List): boolean { +// return compareList(this, other) === 0; +// } - public CompareTo(other: List): number { - return compareList(this, other); - } -} +// public CompareTo(other: List): number { +// return compareList(this, other); +// } +// } export class Union extends SystemObject implements IComparable { public tag: number; diff --git a/src/fable-standalone/test/bench-compiler/package.json b/src/fable-standalone/test/bench-compiler/package.json index b13e897170..02f4a7d9f6 100644 --- a/src/fable-standalone/test/bench-compiler/package.json +++ b/src/fable-standalone/test/bench-compiler/package.json @@ -34,7 +34,9 @@ "tsc-lib-init": "npm run tsc -- --init --target es2020 --module es2020 --allowJs", "tsc-lib": "npm run tsc -- -p ./out-lib --outDir ./out-lib-js", + "build-test-node": "node out-node/app.js ../../../../../fable-test/fable-test.fsproj out-test && npm run build-test-transform", "build-test-node-ts": "node out-node/app.js ../../../../../fable-test/fable-test.fsproj out-test --classTypes --typescript && npm run build-test-transform -- --typescript", + "build-test-dotnet": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test && npm run build-test-transform", "build-test-dotnet-ts": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test --classTypes --typescript && npm run build-test-transform -- --typescript", "build-test-dotnet-opt": "dotnet run -c Release ../../../../../fable-test/fable-test.fsproj out-test --optimize-fcs && npm run build-test-transform", "build-test-transform": "node transform ../../../../../fable-test/fable-test.fsproj out-test ../../../../build/fable-library", diff --git a/tests/Main/ComparisonTests.fs b/tests/Main/ComparisonTests.fs index a9d1b23fd2..190858f800 100644 --- a/tests/Main/ComparisonTests.fs +++ b/tests/Main/ComparisonTests.fs @@ -434,6 +434,10 @@ let tests = (OTest(1).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual (OTest(2).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual + testCase "GetHashCode with objects that overwrite it works" <| fun () -> + (Test(1).GetHashCode(), Test(1).GetHashCode()) ||> equal + (Test(2).GetHashCode(), Test(1).GetHashCode()) ||> notEqual + testCase "GetHashCode with same object works" <| fun () -> let o = OTest(1) let h1 = o.GetHashCode() From 806cd6306062b3c93c64ca440a976fd96d52dc0a Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Tue, 4 Aug 2020 19:42:34 -0700 Subject: [PATCH 2/5] List module update --- src/fable-library/List.fs | 38 ++++++++++++++++----------------- src/fable-library/MutableMap.fs | 9 ++++---- src/fable-library/MutableSet.fs | 9 ++++---- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index c84aad202b..e88531e995 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -11,8 +11,8 @@ let msgListNoMatch = "List did not contain any matching elements" type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = let mutable hashCode = None - static member Empty = new List<'T>(0, ResizeArray<'T>()) - static member internal Cons (x: 'T, xs: 'T list) = xs.Add(x) + static member inline Empty = new List<'T>(0, ResizeArray<'T>()) + static member inline internal Cons (x: 'T, xs: 'T list) = xs.Add(x) member inline internal _.Add(x: 'T) = let values = @@ -25,12 +25,12 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = member inline _.IsEmpty = Count <= 0 member inline _.Length = Count - member _.Head = + member inline _.Head = if Count > 0 then Values.[Count - 1] else failwith msgListWasEmpty - member _.Tail = + member inline _.Tail = if Count > 0 then new List<'T>(Count - 1, Values) else failwith msgListWasEmpty @@ -67,22 +67,22 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = interface System.Collections.Generic.IEnumerable<'T> with member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = - let elems = seq { for i=xs.Length - 1 downto 0 do yield Values.[i] } - elems.GetEnumerator() + // let elems = seq { for i=xs.Length - 1 downto 0 do yield Values.[i] } + // elems.GetEnumerator() // new ListEnumerator<'T>(xs) :> System.Collections.Generic.IEnumerator<'T> - // let mutable i = Count - // { - // new System.Collections.Generic.IEnumerator<'T> with - // member _.Current = Values.[i] - // interface System.Collections.IEnumerator with - // member _.Current: obj = box (Values.[i]) - // member _.MoveNext() = i <- i - 1; i >= 0 - // member _.Reset() = i <- Count - // interface System.IDisposable with - // member _.Dispose(): unit = () - // } + let mutable i = Count + { + new System.Collections.Generic.IEnumerator<'T> with + member _.Current = Values.[i] + interface System.Collections.IEnumerator with + member _.Current: obj = box (Values.[i]) + member _.MoveNext() = i <- i - 1; i >= 0 + member _.Reset() = i <- Count + interface System.IDisposable with + member _.Dispose(): unit = () + } interface System.Collections.IEnumerable with member xs.GetEnumerator(): System.Collections.IEnumerator = @@ -170,7 +170,7 @@ let reverse (xs: 'a list) = fold (fun acc x -> cons x acc) List.Empty xs let toSeq (xs: 'a list): 'a seq = - Seq.map id xs + xs :> System.Collections.Generic.IEnumerable<'a> let ofSeq (xs: 'a seq): 'a list = Seq.fold (fun acc x -> cons x acc) List.Empty xs @@ -201,7 +201,7 @@ let scanBack f (xs: 'a list) (state: 'acc) = Seq.scanBack f xs state |> ofSeq let append (xs: 'a list) (ys: 'a list) = - fold (fun acc x -> cons x acc) ys (reverse xs) + foldBack (fun x acc -> cons x acc) xs ys let collect (f: 'a -> 'b list) (xs: 'a list) = Seq.collect f xs |> ofSeq diff --git a/src/fable-library/MutableMap.fs b/src/fable-library/MutableMap.fs index 5f6e66eb3c..24588063fb 100644 --- a/src/fable-library/MutableMap.fs +++ b/src/fable-library/MutableMap.fs @@ -91,10 +91,11 @@ type MutableMap<'Key, 'Value when 'Key: equality>(pairs: KeyValuePair<'Key, 'Val interface IEnumerable> with member this.GetEnumerator(): IEnumerator> = - let elems = seq { - for pairs in hashMap.Values do - for pair in pairs do - yield pair } + // let elems = seq { + // for pairs in hashMap.Values do + // for pair in pairs do + // yield pair } + let elems = Seq.concat hashMap.Values elems.GetEnumerator() interface ICollection> with diff --git a/src/fable-library/MutableSet.fs b/src/fable-library/MutableSet.fs index d744a49eaf..6e6cbeb970 100644 --- a/src/fable-library/MutableSet.fs +++ b/src/fable-library/MutableSet.fs @@ -79,10 +79,11 @@ type MutableSet<'T when 'T: equality>(items: 'T seq, comparer: IEqualityComparer interface IEnumerable<'T> with member this.GetEnumerator(): IEnumerator<'T> = - let elems = seq { - for values in hashMap.Values do - for value in values do - yield value } + // let elems = seq { + // for values in hashMap.Values do + // for value in values do + // yield value } + let elems = Seq.concat hashMap.Values elems.GetEnumerator() interface ICollection<'T> with From 3c1d85016eb7f53c53b2edacec580a16a799d030 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Tue, 11 Aug 2020 14:06:08 -0700 Subject: [PATCH 3/5] Optimized IEnumerator.get_Current --- src/Fable.Transforms/FSharp2Fable.Util.fs | 5 +- src/Fable.Transforms/FSharp2Fable.fs | 19 ++++-- src/fable-library/List.fs | 74 ++++++----------------- src/fable-library/Seq.ts | 6 +- 4 files changed, 40 insertions(+), 64 deletions(-) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 1d7df4b9eb..156b05d89b 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -1039,7 +1039,10 @@ module Util = | [arg] when memb.IsPropertySetterMethod -> let t = memb.CurriedParameterGroups.[0].[0].Type |> makeType com Map.empty Fable.Set(callee, Fable.FieldSet(name, t), arg, r) - | _ when memb.IsPropertyGetterMethod && countNonCurriedParams memb = 0 -> + | _ when memb.IsPropertyGetterMethod && countNonCurriedParams memb = 0 + // performance optimization, compile get_Current as instance call instead of a getter + && memb.FullName <> "System.Collections.IEnumerator.get_Current" + && memb.FullName <> "System.Collections.Generic.IEnumerator.get_Current" -> let t = memb.ReturnParameter.Type |> makeType com Map.empty let kind = Fable.FieldGet(name, true, t) Fable.Get(callee, kind, typ, r) diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index dbaf0aefbd..163ed010f0 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -171,7 +171,7 @@ let private transformTraitCall com (ctx: Context) r typ (sourceTypes: FSharpType let private transformObjExpr (com: IFableCompiler) (ctx: Context) (objType: FSharpType) baseCallExpr (overrides: FSharpObjectExprOverride list) otherOverrides = - let mapOverride (over: FSharpObjectExprOverride) = + let mapOverride (typ: FSharpType) (over: FSharpObjectExprOverride) = trampoline { let ctx, args = bindMemberArgs com ctx over.CurriedParameterGroups let! body = transformExpr com ctx over.Body @@ -179,7 +179,12 @@ let private transformObjExpr (com: IFableCompiler) (ctx: Context) (objType: FSha let name, kind = match over.Signature.Name with | Naming.StartsWith "get_" name when countNonCurriedParamsForOverride over = 0 -> - name, Fable.ObjectGetter + // performance optimization, compile get_Current as instance call instead of a getter + if over.Signature.Name = "get_Current" && + (typ.TypeDefinition.FullName = "System.Collections.Generic.IEnumerator`1" || + typ.TypeDefinition.FullName = "System.Collections.IEnumerator") + then name, Fable.ObjectMethod false + else name, Fable.ObjectGetter | Naming.StartsWith "set_" name when countNonCurriedParamsForOverride over = 1 -> name, Fable.ObjectSetter | name -> @@ -222,8 +227,8 @@ let private transformObjExpr (com: IFableCompiler) (ctx: Context) (objType: FSha let! members = (objType, overrides)::otherOverrides - |> trampolineListMap (fun (_typ, overrides) -> - overrides |> trampolineListMap mapOverride) + |> trampolineListMap (fun (typ, overrides) -> + overrides |> trampolineListMap (mapOverride typ)) return Fable.ObjectExpr(members |> List.concat, makeType com ctx.GenericArgs objType, baseCall) } @@ -985,7 +990,11 @@ let private transformAttachedMember (com: FableCompiler) (ctx: Context) let kind = match args with | [_thisArg; unitArg] when memb.IsPropertyGetterMethod && unitArg.Type = Fable.Unit -> - Fable.ObjectGetter + // performance optimization, compile get_Current as instance call instead of a getter + if (memb.CompiledName = "System-Collections-Generic-IEnumerator`1-get_Current" || + memb.CompiledName = "System-Collections-IEnumerator-get_Current") + then Fable.ObjectMethod false + else Fable.ObjectGetter | [_thisArg; _valueArg] when memb.IsPropertySetterMethod -> Fable.ObjectSetter | _ when memb.CompiledName = "System-Collections-Generic-IEnumerable`1-GetEnumerator" -> diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index e88531e995..e3e4d7406e 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -11,10 +11,10 @@ let msgListNoMatch = "List did not contain any matching elements" type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = let mutable hashCode = None - static member inline Empty = new List<'T>(0, ResizeArray<'T>()) - static member inline internal Cons (x: 'T, xs: 'T list) = xs.Add(x) + static member Empty = new List<'T>(0, ResizeArray<'T>()) + static member internal Cons (x: 'T, xs: 'T list) = xs.Add(x) - member inline internal _.Add(x: 'T) = + member internal _.Add(x: 'T) = let values = if Count = Values.Count then Values @@ -22,20 +22,20 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = values.Add(x) new List<'T>(values.Count, values) - member inline _.IsEmpty = Count <= 0 - member inline _.Length = Count + member _.IsEmpty = Count <= 0 + member _.Length = Count - member inline _.Head = + member _.Head = if Count > 0 then Values.[Count - 1] else failwith msgListWasEmpty - member inline _.Tail = + member _.Tail = if Count > 0 then new List<'T>(Count - 1, Values) else failwith msgListWasEmpty - member inline _.Item with get(index) = + member _.Item with get(index) = Values.[Count - 1 - index] override xs.ToString() = @@ -46,7 +46,6 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = if xs.Length <> ys.Length then false elif xs.GetHashCode() <> ys.GetHashCode() then false else Seq.forall2 (Unchecked.equals) xs ys - // else (xs :> System.IComparable).CompareTo(other) = 0 override xs.GetHashCode() = match hashCode with @@ -63,59 +62,25 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = interface System.IComparable with member xs.CompareTo(other: obj) = Seq.compareWith compare xs (other :?> 'T list) - // List.CompareWith compare xs (other :?> 'T list) interface System.Collections.Generic.IEnumerable<'T> with member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = - // let elems = seq { for i=xs.Length - 1 downto 0 do yield Values.[i] } - // elems.GetEnumerator() - - // new ListEnumerator<'T>(xs) :> System.Collections.Generic.IEnumerator<'T> - - let mutable i = Count - { - new System.Collections.Generic.IEnumerator<'T> with - member _.Current = Values.[i] - interface System.Collections.IEnumerator with - member _.Current: obj = box (Values.[i]) - member _.MoveNext() = i <- i - 1; i >= 0 - member _.Reset() = i <- Count - interface System.IDisposable with - member _.Dispose(): unit = () - } + new ListEnumerator<'T>(xs) :> System.Collections.Generic.IEnumerator<'T> interface System.Collections.IEnumerable with member xs.GetEnumerator(): System.Collections.IEnumerator = ((xs :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() :> System.Collections.IEnumerator) - // static member internal CompareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = - // if obj.ReferenceEquals(xs, ys) - // then 0 - // else - // if xs.IsEmpty then - // if ys.IsEmpty then 0 else -1 - // elif ys.IsEmpty then 1 - // else - // let mutable i = 0 - // let mutable result = 0 - // if xs.Length > ys.Length then 1 - // elif xs.Length < ys.Length then -1 - // else - // while i < xs.Length && result = 0 do - // result <- comparer xs.[i] ys.[i] - // i <- i + 1 - // result - -// and ListEnumerator<'T when 'T: comparison>(xs: List<'T>) = -// let mutable i = -1 -// interface System.Collections.Generic.IEnumerator<'T> with -// member __.Current = xs.[i] -// interface System.Collections.IEnumerator with -// member __.Current = box (xs.[i]) -// member __.MoveNext() = i <- i + 1; i < xs.Length -// member __.Reset() = i <- -1 -// interface System.IDisposable with -// member __.Dispose() = () +and ListEnumerator<'T when 'T: comparison>(xs: List<'T>) = + let mutable i = -1 + interface System.Collections.Generic.IEnumerator<'T> with + member __.Current = xs.[i] + interface System.Collections.IEnumerator with + member __.Current = box (xs.[i]) + member __.MoveNext() = i <- i + 1; i < xs.Length + member __.Reset() = i <- -1 + interface System.IDisposable with + member __.Dispose() = () and 'T list when 'T: comparison = List<'T> @@ -152,7 +117,6 @@ let tryLast (xs: 'T list) = let compareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = Seq.compareWith comparer xs ys - //List.CompareWith comparer xs ys let fold (folder: 'acc -> 'T -> 'acc) (state: 'acc) (xs: 'T list) = let mutable acc = state diff --git a/src/fable-library/Seq.ts b/src/fable-library/Seq.ts index 5a8f9dae20..c2ab165dcc 100644 --- a/src/fable-library/Seq.ts +++ b/src/fable-library/Seq.ts @@ -4,7 +4,7 @@ import { Option, some, value } from "./Option"; import { compare, equals, IComparer, IDisposable } from "./Util"; export interface IEnumerator { - Current: T | undefined; + Current(): T | undefined; // intentionally not a getter (for performance reasons) MoveNext(): boolean; Reset(): void; } @@ -32,7 +32,7 @@ export class Enumerator implements IEnumerator, IDisposable { this.current = cur.value; return !cur.done; } - get Current() { + Current() { return this.current; } public Reset() { @@ -51,7 +51,7 @@ export function toIterator(en: IEnumerator): Iterator { return { next() { return en.MoveNext() - ? { done: false, value: en.Current } + ? { done: false, value: en.Current() } : { done: true, value: undefined }; }, } as Iterator; From cdf6b30228f53d51f065787079cbee1051286d72 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Tue, 11 Aug 2020 15:55:55 -0700 Subject: [PATCH 4/5] Optimized List.reverse --- src/fable-library/List.fs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index e3e4d7406e..85ea78b2d2 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -22,6 +22,17 @@ type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = values.Add(x) new List<'T>(values.Count, values) + member internal _.Reverse() = + let values = Values.GetRange(0, Count) // copy values + values.Reverse() + new List<'T>(values.Count, values) + + // This is a destructive internal optimization that + // can only be performed on newly constructed lists. + member internal xs.ReverseInPlace() = + Values.Reverse() + xs + member _.IsEmpty = Count <= 0 member _.Length = Count @@ -131,18 +142,21 @@ let foldBack (folder: 'T -> 'acc -> 'acc) (xs: 'T list) (state: 'acc) = acc let reverse (xs: 'a list) = - fold (fun acc x -> cons x acc) List.Empty xs + xs.Reverse() + +let reverseInPlace (xs: 'a list) = + xs.ReverseInPlace() let toSeq (xs: 'a list): 'a seq = xs :> System.Collections.Generic.IEnumerable<'a> let ofSeq (xs: 'a seq): 'a list = Seq.fold (fun acc x -> cons x acc) List.Empty xs - |> reverse + |> reverseInPlace let concat (lists: seq<'a list>) = Seq.fold (fold (fun acc x -> cons x acc)) List.Empty lists - |> reverse + |> reverseInPlace let fold2 f (state: 'acc) (xs: 'a list) (ys: 'b list) = Seq.fold2 f state xs ys @@ -153,10 +167,9 @@ let foldBack2 f (xs: 'a list) (ys: 'b list) (state: 'acc) = let unfold (gen: 'acc -> ('T * 'acc) option) (state: 'acc) = let rec loop st acc = match gen st with - | None -> acc + | None -> reverse acc | Some (x, st) -> loop st (cons x acc) loop state List.Empty - |> reverse let scan f (state: 'acc) (xs: 'a list) = Seq.scan f state xs |> ofSeq @@ -174,9 +187,8 @@ let mapIndexed (f: int -> 'a -> 'b) (xs: 'a list) = let rec loop i acc = if i < xs.Length then loop (i + 1) (cons (f i xs.[i]) acc) - else acc + else reverseInPlace acc loop 0 List.Empty - |> reverse let map (f: 'a -> 'b) (xs: 'a list) = mapIndexed (fun i x -> f x) xs @@ -304,7 +316,7 @@ let filter f xs = if f x then cons x acc else acc) List.Empty xs - |> reverse + |> reverseInPlace let partition f xs = fold (fun (lacc, racc) x -> @@ -315,7 +327,8 @@ let choose f xs = fold (fun acc x -> match f x with | Some y -> cons y acc - | None -> acc) List.Empty xs |> reverse + | None -> acc) List.Empty xs + |> reverseInPlace let contains (value: 'T) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome @@ -330,7 +343,7 @@ let initialize n f = let mutable res = List.Empty for i = 0 to n - 1 do res <- cons (f i) res - res |> reverse + res |> reverseInPlace let replicate n x = initialize n (fun _ -> x) From 231304774500412a224e0bb42295b116200d5439 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Sun, 16 Aug 2020 20:06:21 -0700 Subject: [PATCH 5/5] Fixed Seq.compareWith --- src/Fable.Transforms/Fable2Babel.fs | 9 ++- src/Fable.Transforms/Replacements.fs | 8 ++- src/fable-library/List.fs | 94 +++++++++++++++------------- src/fable-library/Seq.ts | 14 ++++- 4 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 094ee178cb..4e3fd22710 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -810,9 +810,13 @@ module Util = | Replacements.ListLiteral(exprs, t) -> [|List.rev exprs |> makeArray com ctx|] |> coreLibCall com ctx r "List" "newList" + // match exprs with + // | [] -> coreLibCall com ctx r "List" "empty" [||] + // | [TransformExpr com ctx expr] -> coreLibCall com ctx r "List" "singleton" [|expr|] + // | exprs -> [|makeArray com ctx exprs|] |> coreLibCall com ctx r "List" "ofArray" | Fable.NewList (headAndTail, _) -> match headAndTail with - | None -> coreLibCall com ctx r "List" "newList" [||] + | None -> coreLibCall com ctx r "List" "empty" [||] | Some(TransformExpr com ctx head, TransformExpr com ctx tail) -> coreLibCall com ctx r "List" "cons" [|head; tail|] | Fable.NewOption (value, t) -> @@ -1054,6 +1058,8 @@ module Util = let expr = com.TransformAsExpr(ctx, fableExpr) match getKind with | Fable.ExprGet(TransformExpr com ctx prop) -> getExpr range expr prop + // | Fable.ListHead -> get range expr "Head" + // | Fable.ListTail -> get range expr "Tail" | Fable.ListHead -> coreLibCall com ctx range "List" "head" [|expr|] | Fable.ListTail -> coreLibCall com ctx range "List" "tail" [|expr|] | Fable.FieldGet(fieldName,_,_) -> @@ -1212,6 +1218,7 @@ module Util = let op = if nonEmpty then BinaryUnequal else BinaryEqual upcast BinaryExpression(op, com.TransformAsExpr(ctx, expr), NullLiteral(), ?loc=range) | Fable.ListTest nonEmpty -> + // let expr = get range (com.TransformAsExpr(ctx, expr)) "IsEmpty" let expr = coreLibCall com ctx range "List" "isEmpty" [|com.TransformAsExpr(ctx, expr)|] if nonEmpty then upcast UnaryExpression(UnaryNot, expr, ?loc=range) else expr diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 3819f885ac..50d8ccba88 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1842,8 +1842,9 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = let meth = Naming.removeGetSetPrefix i.CompiledName |> Naming.lowerFirst match i.CompiledName, thisArg, args with - | ("get_Head" | "get_Tail" | "get_Length" | "get_IsEmpty"), Some x, _ -> + | ("get_Head" | "get_Tail" | "get_IsEmpty" | "get_Length"), Some x, _ -> Helper.CoreCall("List", meth, t, [x], i.SignatureArgTypes, ?loc=r) |> Some + // get r t x meth |> Some | ("get_Item" | "GetSlice"), Some x, _ -> Helper.CoreCall("List", meth, t, args @ [x], i.SignatureArgTypes, ?loc=r) |> Some | ("get_Empty" | "Cons"), None, _ -> @@ -1854,6 +1855,11 @@ let lists (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Ex let listModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName, args with + // | ("Head" | "Tail" | "IsEmpty") as meth, [x] -> get r t x (Naming.lowerFirst meth) |> Some + // | "IsEmpty", [x] -> Test(x, ListTest false, r) |> Some + // | "Empty", _ -> NewList(None, (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some + // | "Singleton", [x] -> + // NewList(Some(x, Value(NewList(None, t), None)), (genArg com ctx r 0 i.GenericArgs)) |> makeValue r |> Some // Use a cast to give it better chances of optimization (e.g. converting list // literals to arrays) after the beta reduction pass | "ToSeq", [x] -> toSeq t x |> Some diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index 85ea78b2d2..9308a53759 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -8,67 +8,71 @@ open Fable.Core let msgListWasEmpty = "List was empty" let msgListNoMatch = "List did not contain any matching elements" -type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = - let mutable hashCode = None +[] +type List<'T when 'T: comparison> = + { Count: int; Values: ResizeArray<'T> } - static member Empty = new List<'T>(0, ResizeArray<'T>()) - static member internal Cons (x: 'T, xs: 'T list) = xs.Add(x) - - member internal _.Add(x: 'T) = + member inline internal xs.Add(x: 'T) = let values = - if Count = Values.Count - then Values - else Values.GetRange(0, Count) + if xs.Count = xs.Values.Count + then xs.Values + else xs.Values.GetRange(0, xs.Count) values.Add(x) - new List<'T>(values.Count, values) + { Count = values.Count; Values = values } - member internal _.Reverse() = - let values = Values.GetRange(0, Count) // copy values + member inline internal xs.Reverse() = + let values = xs.Values.GetRange(0, xs.Count) // copy values values.Reverse() - new List<'T>(values.Count, values) + { Count = values.Count; Values = values } // This is a destructive internal optimization that // can only be performed on newly constructed lists. - member internal xs.ReverseInPlace() = - Values.Reverse() + member inline internal xs.ReverseInPlace() = + xs.Values.Reverse() xs - member _.IsEmpty = Count <= 0 - member _.Length = Count + static member inline Singleton(x: 'T) = + let values = ResizeArray<'T>() + values.Add(x) + { Count = 1; Values = values } + + static member inline Empty = { Count = 0; Values = ResizeArray<'T>() } + static member inline Cons (x: 'T, xs: 'T list) = xs.Add(x) + + member inline xs.IsEmpty = xs.Count <= 0 + member inline xs.Length = xs.Count - member _.Head = - if Count > 0 - then Values.[Count - 1] + member inline xs.Head = + if xs.Count > 0 + then xs.Values.[xs.Count - 1] else failwith msgListWasEmpty - member _.Tail = - if Count > 0 - then new List<'T>(Count - 1, Values) + member inline xs.Tail = + if xs.Count > 0 + then { Count = xs.Count - 1; Values = xs.Values } else failwith msgListWasEmpty - member _.Item with get(index) = - Values.[Count - 1 - index] + member inline xs.Item with get (index: int) = + xs.Values.[xs.Count - 1 - index] override xs.ToString() = "[" + System.String.Join("; ", xs) + "]" override xs.Equals(other: obj) = - let ys = other :?> 'T list - if xs.Length <> ys.Length then false - elif xs.GetHashCode() <> ys.GetHashCode() then false - else Seq.forall2 (Unchecked.equals) xs ys + if obj.ReferenceEquals(xs, other) + then true + else + let ys = other :?> 'T list + if xs.Length <> ys.Length then false + else Seq.forall2 (Unchecked.equals) xs ys override xs.GetHashCode() = - match hashCode with - | Some h -> h - | None -> - let inline combineHash i x y = (x <<< 1) + y + 631 * i - let len = min (xs.Length - 1) 18 // limit the hash count - let mutable h = 0 - for i = 0 to len do - h <- combineHash i h (hash xs.[i]) - hashCode <- Some h - h + let inline combineHash i x y = (x <<< 1) + y + 631 * i + let len = min (xs.Length - 1) 18 // limit the hash count + let mutable h = 0 + for i = 0 to len do + h <- combineHash i h (hash xs.[i]) + h interface System.IComparable with member xs.CompareTo(other: obj) = @@ -95,13 +99,13 @@ and ListEnumerator<'T when 'T: comparison>(xs: List<'T>) = and 'T list when 'T: comparison = List<'T> -let newList (values: ResizeArray<'T>) = new List<'T>(values.Count, values) +let newList (values: ResizeArray<'T>) = { Count = values.Count; Values = values } let empty () = List.Empty let cons (x: 'T) (xs: 'T list) = List.Cons (x, xs) -let singleton x = cons x List.Empty +let singleton (x: 'T) = List.Singleton (x) let isEmpty (xs: 'T list) = xs.IsEmpty @@ -144,7 +148,7 @@ let foldBack (folder: 'T -> 'acc -> 'acc) (xs: 'T list) (state: 'acc) = let reverse (xs: 'a list) = xs.Reverse() -let reverseInPlace (xs: 'a list) = +let inline reverseInPlace (xs: 'a list) = xs.ReverseInPlace() let toSeq (xs: 'a list): 'a seq = @@ -167,7 +171,7 @@ let foldBack2 f (xs: 'a list) (ys: 'b list) (state: 'acc) = let unfold (gen: 'acc -> ('T * 'acc) option) (state: 'acc) = let rec loop st acc = match gen st with - | None -> reverse acc + | None -> reverseInPlace acc | Some (x, st) -> loop st (cons x acc) loop state List.Empty @@ -210,7 +214,7 @@ let mapFold (f: 'S -> 'T -> 'R * 'S) s xs = let nx, fs = f fs x cons nx nxs, fs let nxs, s = fold folder (List.Empty, s) xs - reverse nxs, s + reverseInPlace nxs, s let mapFoldBack (f: 'T -> 'S -> 'R * 'S) xs s = mapFold (fun s v -> f v s) s (reverse xs) @@ -492,7 +496,7 @@ let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collect dict.Add(key, cons v List.Empty) keys <- cons key keys ) let mutable result = List.Empty - keys |> iterate (fun key -> result <- cons (key, reverse dict.[key]) result) + keys |> iterate (fun key -> result <- cons (key, reverseInPlace dict.[key]) result) result let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = diff --git a/src/fable-library/Seq.ts b/src/fable-library/Seq.ts index c2ab165dcc..40c2aa03d1 100644 --- a/src/fable-library/Seq.ts +++ b/src/fable-library/Seq.ts @@ -191,8 +191,18 @@ export function choose(f: (x: T) => U, xs: Iterable) { } export function compareWith(f: (x: T, y: T) => number, xs: Iterable, ys: Iterable) { - const nonZero = tryFind((i: number) => i !== 0, map2(f, xs, ys)); - return nonZero != null ? value(nonZero) : length(xs) - length(ys); + if (xs === ys) { return 0; } + let cur1: IteratorResult; + let cur2: IteratorResult; + let c = 0; + for (const iter1 = xs[Symbol.iterator](), iter2 = ys[Symbol.iterator](); ;) { + cur1 = iter1.next(); + cur2 = iter2.next(); + if (cur1.done || cur2.done) { break; } + c = f(cur1.value, cur2.value); + if (c !== 0) { break; } + } + return (c !== 0) ? c : (cur1.done && !cur2.done) ? -1 : (!cur1.done && cur2.done) ? 1 : 0; } export function delay(f: () => Iterable): Iterable {