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.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 796853a0cd..4e3fd22710 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,16 @@ 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" + // 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 "Types" "newList" [||] + | None -> coreLibCall com ctx r "List" "empty" [||] | 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 +1058,10 @@ 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 -> 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 +1157,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 +1218,8 @@ 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 = 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..50d8ccba88 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,26 @@ 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_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, _ -> 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 + // | ("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/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..9308a53759 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -3,157 +3,218 @@ 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> } + + member inline internal xs.Add(x: 'T) = + let values = + if xs.Count = xs.Values.Count + then xs.Values + else xs.Values.GetRange(0, xs.Count) + values.Add(x) + { Count = values.Count; Values = values } + + member inline internal xs.Reverse() = + let values = xs.Values.GetRange(0, xs.Count) // copy values + values.Reverse() + { Count = values.Count; Values = values } + + // This is a destructive internal optimization that + // can only be performed on newly constructed lists. + member inline internal xs.ReverseInPlace() = + xs.Values.Reverse() + xs + + 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 inline xs.Head = + if xs.Count > 0 + then xs.Values.[xs.Count - 1] + else failwith msgListWasEmpty + + member inline xs.Tail = + if xs.Count > 0 + then { Count = xs.Count - 1; Values = xs.Values } + else failwith msgListWasEmpty + + 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) = + 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() = + 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) = + Seq.compareWith compare xs (other :?> 'T list) + + interface System.Collections.Generic.IEnumerable<'T> with + member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = + 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) + +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>) = { Count = values.Count; Values = values } + +let empty () = List.Empty + +let cons (x: 'T) (xs: 'T list) = List.Cons (x, xs) + +let singleton (x: 'T) = List.Singleton (x) + +let isEmpty (xs: 'T list) = xs.IsEmpty + +let length (xs: 'T list) = xs.Length -let tryHead = function - | x::_ -> Some x - | _ -> None +let head (xs: 'T list) = xs.Head -let tail = function - | _::xs -> xs - | _ -> failwith "List was empty" +let tryHead (xs: 'T list) = + if xs.Length > 0 + then Some xs.[0] + else None -let rec last = function - | [] -> failwith "List was empty" - | [x] -> x - | _::xs -> last xs +let tail (xs: 'T list) = xs.Tail -let rec tryLast = function - | [] -> None - | [x] -> Some x - | _::xs -> tryLast xs +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 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) + Seq.compareWith comparer xs ys + +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 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 reverse (xs: 'a list) = + xs.Reverse() + +let inline reverseInPlace (xs: 'a list) = + xs.ReverseInPlace() 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 -> x::acc) [] xs - |> reverse + Seq.fold (fun acc x -> cons x acc) List.Empty xs + |> reverseInPlace let concat (lists: seq<'a list>) = - Seq.fold (fold (fun acc x -> x::acc)) [] lists - |> reverse + Seq.fold (fold (fun acc x -> cons x acc)) List.Empty lists + |> reverseInPlace -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 -> reverseInPlace acc + | Some (x, st) -> loop st (cons x acc) + loop state List.Empty -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) = + foldBack (fun x acc -> cons x acc) xs ys 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 - |> reverse +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 reverseInPlace acc + loop 0 List.Empty -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 - reverse nxs, s + cons nx nxs, fs + let nxs, s = fold folder (List.Empty, s) xs + reverseInPlace nxs, s let mapFoldBack (f: 'T -> 'S -> 'R * 'S) xs s = mapFold (fun s v -> f v s) s (reverse xs) @@ -165,132 +226,139 @@ 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 + |> reverseInPlace 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 + |> reverseInPlace + +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 |> reverseInPlace 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 +366,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 +387,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 +410,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 +430,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, reverseInPlace 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 +509,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/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 diff --git a/src/fable-library/Seq.ts b/src/fable-library/Seq.ts index 5a8f9dae20..40c2aa03d1 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; @@ -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 { 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()