diff --git a/.vscode/launch.json b/.vscode/launch.json index 3923a67619..7461356674 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -95,9 +95,9 @@ "type": "coreclr", "request": "launch", "name": "bench-compile (.NET)", - "program": "${workspaceFolder}/src/fable-standalone/test/bench-compiler/bin/Debug/netcoreapp2.1/bench-compiler.dll", + "program": "${workspaceFolder}/src/fable-standalone/test/bench-compiler/bin/Debug/netcoreapp3.1/bench-compiler.dll", // "args": ["${workspaceRoot}/tests/Main/Fable.Tests.fsproj", "out-tests", "--commonjs"], - "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test", "--typescript"], + "args": ["${workspaceRoot}/../fable-test/fable-test.fsproj", "out-test"], "cwd": "${workspaceFolder}/src/fable-standalone/test/bench-compiler" }, { diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index ed801b8de3..525a1a4165 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -1203,6 +1203,10 @@ module Util = else // Indexed properties keep the get_/set_ prefix and are compiled as methods if indexedProp then memb.CompiledName, false, false + // performance optimization, compile get_Current as instance call instead of a getter + elif memb.FullName = "System.Collections.IEnumerator.get_Current" || + memb.FullName = "System.Collections.Generic.IEnumerator.get_Current" + then getMemberDisplayName memb, false, false else getMemberDisplayName memb, isGetter, isSetter if isGetter then let t = memb.ReturnParameter.Type |> makeType Map.empty diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index e032b1a6d6..5532da6940 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -206,6 +206,11 @@ let private getAttachedMemberInfo com ctx r nonMangledNameConflicts let name, isGetter, isSetter = // For indexed properties, keep the get_/set_ prefix and compile as method if indexedProp then sign.Name, false, false + // performance optimization, compile get_Current as instance call instead of a getter + elif sign.Name = "get_Current" && + (fullName = Some "System.Collections.IEnumerator" || + fullName = Some "System.Collections.Generic.IEnumerator`1") + then Naming.removeGetSetPrefix sign.Name, false, false else Naming.removeGetSetPrefix sign.Name, isGetter, isSetter // Setters can have same name as getters, assume there will always be a getter if not isSetter && nonMangledNameConflicts declaringEntityName name then diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 36adaf3d0c..7401c9776e 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -439,7 +439,7 @@ module Annotation = makeNativeTypeAnnotation com ctx [genArg] "Array" let makeListTypeAnnotation com ctx genArg = - makeImportTypeAnnotation com ctx [genArg] "Types" "List" + makeImportTypeAnnotation com ctx [genArg] "List" "List" let makeUnionTypeAnnotation com ctx genArgs = List.map (typeAnnotation com ctx) genArgs @@ -655,12 +655,6 @@ module Util = | [] -> expr | m::ms -> get None expr m |> getParts ms - let makeList com ctx headAndTail = - match headAndTail with - | None -> [||] - | Some(TransformExpr com ctx head, TransformExpr com ctx tail) -> [|head; tail|] - |> libConsCall com ctx "Types" "List" - let makeArray (com: IBabelCompiler) ctx exprs = List.mapToArray (fun e -> com.TransformAsExpr(ctx, e)) exprs |> ArrayExpression :> Expression @@ -893,12 +887,17 @@ module Util = | Fable.NewTuple vals -> makeArray com ctx vals // Optimization for bundle size: compile list literals as List.ofArray | Replacements.ListLiteral(exprs, t) -> - match exprs with - | [] -> makeList com ctx None - | [expr] -> Some(expr, Fable.Value(Fable.NewList (None,t), None)) |> makeList com ctx - | exprs -> [|makeArray com ctx exprs|] |> libCall com ctx r "List" "ofArray" + [|List.rev exprs |> makeArray com ctx|] + |> libCall com ctx r "List" "newList" + // match exprs with + // | [] -> libCall com ctx r "List" "empty" [||] + // | [TransformExpr com ctx expr] -> libCall com ctx r "List" "singleton" [|expr|] + // | exprs -> [|makeArray com ctx exprs|] |> libCall com ctx r "List" "ofArray" | Fable.NewList (headAndTail, _) -> - makeList com ctx headAndTail + match headAndTail with + | None -> libCall com ctx r "List" "empty" [||] + | Some(TransformExpr com ctx head, TransformExpr com ctx tail) -> + libCall com ctx r "List" "cons" [|head; tail|] | Fable.NewOption (value, t) -> match value with | Some (TransformExpr com ctx e) -> @@ -1086,8 +1085,10 @@ module Util = match getKind with | Fable.ByKey(Fable.ExprKey(TransformExpr com ctx prop)) -> getExpr range expr prop | Fable.ByKey(Fable.FieldKey field) -> get range expr field.Name - | 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 -> libCall com ctx range "List" "head" [|expr|] + | Fable.ListTail -> libCall com ctx range "List" "tail" [|expr|] | Fable.TupleIndex index -> getExpr range expr (ofInt index) | Fable.OptionValue -> if mustWrapOption typ || com.Options.Typescript @@ -1143,9 +1144,9 @@ 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 = com.TransformAsExpr(ctx, expr) - let op = if nonEmpty then BinaryUnequal else BinaryEqual - upcast BinaryExpression(op, get None expr "tail", NullLiteral(), ?loc=range) + // let expr = get range (com.TransformAsExpr(ctx, expr)) "IsEmpty" + let expr = libCall com ctx range "List" "isEmpty" [|com.TransformAsExpr(ctx, expr)|] + if nonEmpty then upcast UnaryExpression(UnaryNot, expr, ?loc=range) else expr | Fable.UnionCaseTest tag -> let expected = ofInt tag let actual = com.TransformAsExpr(ctx, expr) |> getUnionExprTag None diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 1aeedeb2a3..493148ad44 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1664,6 +1664,8 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this Helper.LibCall(com, "Seq", "filter", t, [arg; ar], ?loc=r) |> toArray com t |> Some | "AddRange", Some ar, [arg] -> Helper.LibCall(com, "Array", "addRangeInPlace", t, [arg; ar], ?loc=r) |> Some + | "GetRange", Some ar, [idx; cnt] -> + Helper.LibCall(com, "Array", "getSubArray", t, [ar; idx; cnt], ?loc=r) |> Some | "Contains", Some (MaybeCasted(ar)), [arg] -> match ar.Type with | Array _ -> @@ -1771,29 +1773,26 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex Helper.LibCall(com, "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 - | ReplaceName - [ "get_Head", "head" - "get_Tail", "tail" - "get_Item", "item" - "get_Length", "length" - "GetSlice", "slice" ] methName, Some x, _ -> - let args = match args with [ExprType Unit] -> [x] | args -> args @ [x] - Helper.LibCall(com, "List", methName, t, args, i.SignatureArgTypes, ?loc=r) |> Some - | "get_IsEmpty", Some x, _ -> Test(x, ListTest false, 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 + | ("get_Head" | "get_Tail" | "get_IsEmpty" | "get_Length"), Some x, _ -> + Helper.LibCall(com, "List", meth, t, [x], i.SignatureArgTypes, ?loc=r) |> Some + // get r t x meth |> Some + | ("get_Item" | "GetSlice"), Some x, _ -> + Helper.LibCall(com, "List", meth, t, args @ [x], i.SignatureArgTypes, ?loc=r) |> Some + | ("get_Empty" | "Cons"), None, _ -> + Helper.LibCall(com, "List", meth, t, args, i.SignatureArgTypes, ?loc=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 @@ -2586,8 +2585,8 @@ let enumerables (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr let enumerators (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg with - | "get_Current", Some x -> get r t x "Current" |> Some - | meth, Some x -> Helper.InstanceCall(x, meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some + // | "get_Current", Some x -> get r t x "Current" |> Some + | meth, Some x -> Helper.InstanceCall(x, Naming.removeGetSetPrefix meth, t, args, i.SignatureArgTypes, ?loc=r) |> Some | _ -> None let events (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/fable-library/Array.fs b/src/fable-library/Array.fs index c469849865..ba11bfb216 100644 --- a/src/fable-library/Array.fs +++ b/src/fable-library/Array.fs @@ -101,8 +101,7 @@ module Helpers = open Helpers -let private indexNotFound() = - failwith "An index satisfying the predicate was not found in the collection." +let private indexNotFoundMsg = "An index satisfying the predicate was not found in the collection." // 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. @@ -445,7 +444,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 @@ -453,7 +452,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 @@ -463,7 +462,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) @@ -480,7 +479,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) @@ -501,7 +500,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 9d1d7d14b2..133160d1f3 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -3,157 +3,228 @@ 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" -let tryHead = function - | x::_ -> Some x - | _ -> None +[] +type List<'T when 'T: comparison> = + { Count: int; Values: ResizeArray<'T> } -let tail = function - | _::xs -> xs - | _ -> failwith "List was empty" + 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 } -let rec last = function - | [] -> failwith "List was empty" - | [x] -> x - | _::xs -> last xs + member inline internal xs.Reverse() = + let values = xs.Values.GetRange(0, xs.Count) // copy values + values.Reverse() + { Count = values.Count; Values = values } -let rec tryLast = function - | [] -> None - | [x] -> Some x - | _::xs -> tryLast xs + // 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 NewList (values: ResizeArray<'T>) = + { Count = values.Count; 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>) = List.NewList (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 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 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 - -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 + Seq.fold (fold (fun acc x -> cons x acc)) List.Empty lists + |> reverseInPlace -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 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 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 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 cons xs ys + let mutable acc = ys + for i = xs.Length - 1 downto 0 do + acc <- cons xs.[i] acc + acc 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 +236,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 +376,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 +397,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 +420,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 +440,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 skipWhile predicate (xs: 'T list) = + Seq.skipWhile predicate 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) + Seq.take i xs |> ofSeq + +let takeWhile predicate (xs: 'T list) = + Seq.takeWhile 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 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 outOfRange() = failwith "Index out of range" - -let slice (lower: int option) (upper: int option) (xs: 'T list) = + 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 +519,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 <- (slice (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/Reflection.ts b/src/fable-library/Reflection.ts index b62c3e8cbc..7e68c3ab4d 100644 --- a/src/fable-library/Reflection.ts +++ b/src/fable-library/Reflection.ts @@ -1,5 +1,5 @@ import { value as getOptionValue } from "./Option"; -import { anonRecord as makeAnonRecord, Record, Union, List } from "./Types"; +import { anonRecord as makeAnonRecord, Record, Union } from "./Types"; import { compareArraysWith, equalArraysWith, isArrayLike } from "./Util"; export type FieldInfo = [string, TypeInfo]; @@ -453,9 +453,9 @@ export function typeTest(x: any, typeTester: TypeTester): boolean { && (x.length === 0 || typeTester[1] == null || typeTest(x[0], typeTester[1])); - case "list": - return x instanceof List - && (x.tail == null || typeTest(x.head, typeTester[1])); + // case "list": + // return x instanceof List + // && (x.tail == null || typeTest(x.head, typeTester[1])); case "option": return x == null || typeTest(getOptionValue(x), typeTester[1]); default: 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 07dee3857c..9dd1c1b3e8 100644 --- a/src/fable-library/Types.ts +++ b/src/fable-library/Types.ts @@ -24,70 +24,139 @@ export class SystemObject implements IEquatable { } } -function compareList(self: List, other: List) { - if (self === other) { - return 0; - } else { - if (other == null) { - return -1; - } - while (self.tail != null) { - if (other.tail == null) { return 1; } - const res = compare(self.head, other.head); - if (res !== 0) { return res; } - self = self.tail; - other = other.tail; - } - return other.tail == null ? 0 : -1; - } -} - -export class List implements IEquatable>, IComparable>, Iterable { - public head: T; - public tail?: List; - - constructor(head?: T, tail?: List) { - this.head = head as T; - this.tail = tail; - } - - public toString() { - return this.ToString(); - } - - public toJSON() { - return Array.from(this); - } - - public [Symbol.iterator](): Iterator { - let cur: List | undefined = this; - return { - next: (): IteratorResult => { - const value = cur?.head as T; - const done = cur?.tail == null; - cur = cur?.tail; - return { done, value }; - }, - }; - } - - public ToString() { - return "[" + Array.from(this).join("; ") + "]"; - } - - public GetHashCode() { - const hashes = Array.from(this).map(structuralHash); - return combineHashCodes(hashes); - } - - public Equals(other: List): boolean { - return compareList(this, other) === 0; - } - - public CompareTo(other: List): number { - return compareList(this, other); - } -} +// 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 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; +// } + +// 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 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 Length(): number { +// return this.idx + 1 + (this.tail?.Length ?? 0); +// } + +// public toString() { +// return "[" + Array.from(this).join("; ") + "]"; +// } + +// 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 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 CompareTo(other: List): number { +// return compareList(this, other); +// } +// } export class Union extends SystemObject implements IComparable { public tag: number; diff --git a/tests/Main/ListTests.fs b/tests/Main/ListTests.fs index 30d0534d95..d679a1acfd 100644 --- a/tests/Main/ListTests.fs +++ b/tests/Main/ListTests.fs @@ -42,13 +42,26 @@ module List = let tests = testList "Lists" [ - // TODO: Empty lists may be represented as null, make sure they don't conflict with None testCase "Some [] works" <| fun () -> let xs: int list option = Some [] let ys: int list option = None Option.isSome xs |> equal true Option.isNone ys |> equal true + testCase "List equality works" <| fun () -> + let xs = [1;2;3] + let ys = [1;2;3] + let zs = [1;4;3] + xs = ys |> equal true + xs = zs |> equal false + + testCase "List comparison works" <| fun () -> + let xs = [1;2;3] + let ys = [1;2;3] + let zs = [1;4;3] + xs < ys |> equal false + xs < zs |> equal true + testCase "Pattern matching with lists works" <| fun () -> match [] with [] -> true | _ -> false |> equal true @@ -302,9 +315,9 @@ let tests = |> List.sum |> equal 9 testCase "List.rev works" <| fun () -> - let xs = [1; 2] + let xs = [1; 2; 3] let ys = xs |> List.rev - equal 2 ys.Head + equal 3 ys.Head testCase "List.scan works" <| fun () -> let xs = [1; 2; 3; 4] diff --git a/tests/Main/ResizeArrayTests.fs b/tests/Main/ResizeArrayTests.fs index 7b2ccc6bee..a630d9e5a7 100644 --- a/tests/Main/ResizeArrayTests.fs +++ b/tests/Main/ResizeArrayTests.fs @@ -131,6 +131,13 @@ let tests = li.AddRange [1;2;3] equal 3 li.Count + testCase "ResizeArray.GetRange works" <| fun () -> + let li = ResizeArray<_>() + li.AddRange [1;2;3] + let sub = li.GetRange(1, 2) + sub.Count |> equal 2 + sub.Contains(1) |> equal false + testCase "ResizeArray.Contains works" <| fun () -> let li = ResizeArray<_>() li.Add("ab")