1(* ========================================================================= *) 2(* ML UTILITY FUNCTIONS *) 3(* Copyright (c) 2001 Joe Hurd, distributed under the BSD License *) 4(* ========================================================================= *) 5 6structure Useful :> Useful = 7struct 8 9(* ------------------------------------------------------------------------- *) 10(* Exceptions. *) 11(* ------------------------------------------------------------------------- *) 12 13exception Error of string; 14 15exception Bug of string; 16 17fun errorToStringOption err = 18 case err of 19 Error message => SOME ("Error: " ^ message) 20 | _ => NONE; 21 22(*mlton 23val () = MLton.Exn.addExnMessager errorToStringOption; 24*) 25 26fun errorToString err = 27 case errorToStringOption err of 28 SOME s => "\n" ^ s ^ "\n" 29 | NONE => raise Bug "errorToString: not an Error exception"; 30 31fun bugToStringOption err = 32 case err of 33 Bug message => SOME ("Bug: " ^ message) 34 | _ => NONE; 35 36(*mlton 37val () = MLton.Exn.addExnMessager bugToStringOption; 38*) 39 40fun bugToString err = 41 case bugToStringOption err of 42 SOME s => "\n" ^ s ^ "\n" 43 | NONE => raise Bug "bugToString: not a Bug exception"; 44 45fun total f x = SOME (f x) handle Error _ => NONE; 46 47fun can f = Option.isSome o total f; 48 49(* ------------------------------------------------------------------------- *) 50(* Tracing. *) 51(* ------------------------------------------------------------------------- *) 52 53local 54 val traceOut = TextIO.stdOut; 55 56 fun tracePrintFn mesg = 57 let 58 val () = TextIO.output (traceOut,mesg) 59 60 val () = TextIO.flushOut traceOut 61 in 62 () 63 end; 64in 65 val tracePrint = ref tracePrintFn; 66end; 67 68fun trace mesg = !tracePrint mesg; 69 70(* ------------------------------------------------------------------------- *) 71(* Combinators. *) 72(* ------------------------------------------------------------------------- *) 73 74fun C f x y = f y x; 75 76fun I x = x; 77 78fun K x y = x; 79 80fun S f g x = f x (g x); 81 82fun W f x = f x x; 83 84fun funpow 0 _ x = x 85 | funpow n f x = funpow (n - 1) f (f x); 86 87fun exp m = 88 let 89 fun f _ 0 z = z 90 | f x y z = f (m (x,x)) (y div 2) (if y mod 2 = 0 then z else m (z,x)) 91 in 92 f 93 end; 94 95(* ------------------------------------------------------------------------- *) 96(* Pairs. *) 97(* ------------------------------------------------------------------------- *) 98 99fun fst (x,_) = x; 100 101fun snd (_,y) = y; 102 103fun pair x y = (x,y); 104 105fun swap (x,y) = (y,x); 106 107fun curry f x y = f (x,y); 108 109fun uncurry f (x,y) = f x y; 110 111val op## = fn (f,g) => fn (x,y) => (f x, g y); 112 113(* ------------------------------------------------------------------------- *) 114(* State transformers. *) 115(* ------------------------------------------------------------------------- *) 116 117val unit : 'a -> 's -> 'a * 's = pair; 118 119fun bind f (g : 'a -> 's -> 'b * 's) = uncurry g o f; 120 121fun mmap f (m : 's -> 'a * 's) = bind m (unit o f); 122 123fun mjoin (f : 's -> ('s -> 'a * 's) * 's) = bind f I; 124 125fun mwhile c b = let fun f a = if c a then bind (b a) f else unit a in f end; 126 127(* ------------------------------------------------------------------------- *) 128(* Equality. *) 129(* ------------------------------------------------------------------------- *) 130 131val equal = fn x => fn y => x = y; 132 133val notEqual = fn x => fn y => x <> y; 134 135fun listEqual xEq = 136 let 137 fun xsEq [] [] = true 138 | xsEq (x1 :: xs1) (x2 :: xs2) = xEq x1 x2 andalso xsEq xs1 xs2 139 | xsEq _ _ = false 140 in 141 xsEq 142 end; 143 144(* ------------------------------------------------------------------------- *) 145(* Comparisons. *) 146(* ------------------------------------------------------------------------- *) 147 148fun mapCompare f cmp (a,b) = cmp (f a, f b); 149 150fun revCompare cmp x_y = 151 case cmp x_y of LESS => GREATER | EQUAL => EQUAL | GREATER => LESS; 152 153fun prodCompare xCmp yCmp ((x1,y1),(x2,y2)) = 154 case xCmp (x1,x2) of 155 LESS => LESS 156 | EQUAL => yCmp (y1,y2) 157 | GREATER => GREATER; 158 159fun lexCompare cmp = 160 let 161 fun lex ([],[]) = EQUAL 162 | lex ([], _ :: _) = LESS 163 | lex (_ :: _, []) = GREATER 164 | lex (x :: xs, y :: ys) = 165 case cmp (x,y) of 166 LESS => LESS 167 | EQUAL => lex (xs,ys) 168 | GREATER => GREATER 169 in 170 lex 171 end; 172 173fun optionCompare _ (NONE,NONE) = EQUAL 174 | optionCompare _ (NONE,_) = LESS 175 | optionCompare _ (_,NONE) = GREATER 176 | optionCompare cmp (SOME x, SOME y) = cmp (x,y); 177 178fun boolCompare (false,true) = LESS 179 | boolCompare (true,false) = GREATER 180 | boolCompare _ = EQUAL; 181 182(* ------------------------------------------------------------------------- *) 183(* Lists. *) 184(* ------------------------------------------------------------------------- *) 185 186fun cons x y = x :: y; 187 188fun hdTl l = (hd l, tl l); 189 190fun append xs ys = xs @ ys; 191 192fun singleton a = [a]; 193 194fun first f [] = NONE 195 | first f (x :: xs) = (case f x of NONE => first f xs | s => s); 196 197fun maps (_ : 'a -> 's -> 'b * 's) [] = unit [] 198 | maps f (x :: xs) = 199 bind (f x) (fn y => bind (maps f xs) (fn ys => unit (y :: ys))); 200 201fun mapsPartial (_ : 'a -> 's -> 'b option * 's) [] = unit [] 202 | mapsPartial f (x :: xs) = 203 bind 204 (f x) 205 (fn yo => 206 bind 207 (mapsPartial f xs) 208 (fn ys => unit (case yo of NONE => ys | SOME y => y :: ys))); 209 210fun zipWith f = 211 let 212 fun z l [] [] = l 213 | z l (x :: xs) (y :: ys) = z (f x y :: l) xs ys 214 | z _ _ _ = raise Error "zipWith: lists different lengths"; 215 in 216 fn xs => fn ys => List.rev (z [] xs ys) 217 end; 218 219fun zip xs ys = zipWith pair xs ys; 220 221local 222 fun inc ((x,y),(xs,ys)) = (x :: xs, y :: ys); 223in 224 fun unzip ab = List.foldl inc ([],[]) (List.rev ab); 225end; 226 227fun cartwith f = 228 let 229 fun aux _ res _ [] = res 230 | aux xsCopy res [] (y :: yt) = aux xsCopy res xsCopy yt 231 | aux xsCopy res (x :: xt) (ys as y :: _) = 232 aux xsCopy (f x y :: res) xt ys 233 in 234 fn xs => fn ys => 235 let val xs' = List.rev xs in aux xs' [] xs' (List.rev ys) end 236 end; 237 238fun cart xs ys = cartwith pair xs ys; 239 240fun takeWhile p = 241 let 242 fun f acc [] = List.rev acc 243 | f acc (x :: xs) = if p x then f (x :: acc) xs else List.rev acc 244 in 245 f [] 246 end; 247 248fun dropWhile p = 249 let 250 fun f [] = [] 251 | f (l as x :: xs) = if p x then f xs else l 252 in 253 f 254 end; 255 256fun divideWhile p = 257 let 258 fun f acc [] = (List.rev acc, []) 259 | f acc (l as x :: xs) = if p x then f (x :: acc) xs else (List.rev acc, l) 260 in 261 f [] 262 end; 263 264fun groups f = 265 let 266 fun group acc row x l = 267 case l of 268 [] => 269 let 270 val acc = if List.null row then acc else List.rev row :: acc 271 in 272 List.rev acc 273 end 274 | h :: t => 275 let 276 val (eor,x) = f (h,x) 277 in 278 if eor then group (List.rev row :: acc) [h] x t 279 else group acc (h :: row) x t 280 end 281 in 282 group [] [] 283 end; 284 285fun groupsBy eq = 286 let 287 fun f (x_y as (x,_)) = (not (eq x_y), x) 288 in 289 fn [] => [] 290 | h :: t => 291 case groups f h t of 292 [] => [[h]] 293 | hs :: ts => (h :: hs) :: ts 294 end; 295 296local 297 fun fstEq ((x,_),(y,_)) = x = y; 298 299 fun collapse l = (fst (hd l), List.map snd l); 300in 301 fun groupsByFst l = List.map collapse (groupsBy fstEq l); 302end; 303 304fun groupsOf n = 305 let 306 fun f (_,i) = if i = 1 then (true,n) else (false, i - 1) 307 in 308 groups f (n + 1) 309 end; 310 311fun index p = 312 let 313 fun idx _ [] = NONE 314 | idx n (x :: xs) = if p x then SOME n else idx (n + 1) xs 315 in 316 idx 0 317 end; 318 319fun enumerate l = fst (maps (fn x => fn m => ((m, x), m + 1)) l 0); 320 321local 322 fun revDiv acc l 0 = (acc,l) 323 | revDiv _ [] _ = raise Subscript 324 | revDiv acc (h :: t) n = revDiv (h :: acc) t (n - 1); 325in 326 fun revDivide l = revDiv [] l; 327end; 328 329fun divide l n = let val (a,b) = revDivide l n in (List.rev a, b) end; 330 331fun updateNth (n,x) l = 332 let 333 val (a,b) = revDivide l n 334 in 335 case b of [] => raise Subscript | _ :: t => List.revAppend (a, x :: t) 336 end; 337 338fun deleteNth n l = 339 let 340 val (a,b) = revDivide l n 341 in 342 case b of [] => raise Subscript | _ :: t => List.revAppend (a,t) 343 end; 344 345(* ------------------------------------------------------------------------- *) 346(* Sets implemented with lists. *) 347(* ------------------------------------------------------------------------- *) 348 349fun mem x = List.exists (equal x); 350 351fun insert x s = if mem x s then s else x :: s; 352 353fun delete x s = List.filter (not o equal x) s; 354 355local 356 fun inc (v,x) = if mem v x then x else v :: x; 357in 358 fun setify s = List.rev (List.foldl inc [] s); 359end; 360 361fun union s t = 362 let 363 fun inc (v,x) = if mem v t then x else v :: x 364 in 365 List.foldl inc t (List.rev s) 366 end; 367 368fun intersect s t = 369 let 370 fun inc (v,x) = if mem v t then v :: x else x 371 in 372 List.foldl inc [] (List.rev s) 373 end; 374 375fun difference s t = 376 let 377 fun inc (v,x) = if mem v t then x else v :: x 378 in 379 List.foldl inc [] (List.rev s) 380 end; 381 382fun subset s t = List.all (fn x => mem x t) s; 383 384fun distinct [] = true 385 | distinct (x :: rest) = not (mem x rest) andalso distinct rest; 386 387(* ------------------------------------------------------------------------- *) 388(* Sorting and searching. *) 389(* ------------------------------------------------------------------------- *) 390 391(* Finding the minimum and maximum element of a list, wrt some order. *) 392 393fun minimum cmp = 394 let 395 fun min (l,m,r) _ [] = (m, List.revAppend (l,r)) 396 | min (best as (_,m,_)) l (x :: r) = 397 min (case cmp (x,m) of LESS => (l,x,r) | _ => best) (x :: l) r 398 in 399 fn [] => raise Empty 400 | h :: t => min ([],h,t) [h] t 401 end; 402 403fun maximum cmp = minimum (revCompare cmp); 404 405(* Merge (for the following merge-sort, but generally useful too). *) 406 407fun merge cmp = 408 let 409 fun mrg acc [] ys = List.revAppend (acc,ys) 410 | mrg acc xs [] = List.revAppend (acc,xs) 411 | mrg acc (xs as x :: xt) (ys as y :: yt) = 412 (case cmp (x,y) of 413 GREATER => mrg (y :: acc) xs yt 414 | _ => mrg (x :: acc) xt ys) 415 in 416 mrg [] 417 end; 418 419(* Merge sort (stable). *) 420 421fun sort cmp = 422 let 423 fun findRuns acc r rs [] = List.rev (List.rev (r :: rs) :: acc) 424 | findRuns acc r rs (x :: xs) = 425 case cmp (r,x) of 426 GREATER => findRuns (List.rev (r :: rs) :: acc) x [] xs 427 | _ => findRuns acc x (r :: rs) xs 428 429 fun mergeAdj acc [] = List.rev acc 430 | mergeAdj acc (xs as [_]) = List.revAppend (acc,xs) 431 | mergeAdj acc (x :: y :: xs) = mergeAdj (merge cmp x y :: acc) xs 432 433 fun mergePairs [xs] = xs 434 | mergePairs l = mergePairs (mergeAdj [] l) 435 in 436 fn [] => [] 437 | l as [_] => l 438 | h :: t => mergePairs (findRuns [] h [] t) 439 end; 440 441fun sortMap _ _ [] = [] 442 | sortMap _ _ (l as [_]) = l 443 | sortMap f cmp xs = 444 let 445 fun ncmp ((m,_),(n,_)) = cmp (m,n) 446 val nxs = List.map (fn x => (f x, x)) xs 447 val nys = sort ncmp nxs 448 in 449 List.map snd nys 450 end; 451 452(* ------------------------------------------------------------------------- *) 453(* Integers. *) 454(* ------------------------------------------------------------------------- *) 455 456fun interval m 0 = [] 457 | interval m len = m :: interval (m + 1) (len - 1); 458 459fun divides _ 0 = true 460 | divides 0 _ = false 461 | divides a b = b mod (Int.abs a) = 0; 462 463local 464 fun hcf 0 n = n 465 | hcf 1 _ = 1 466 | hcf m n = hcf (n mod m) m; 467in 468 fun gcd m n = 469 let 470 val m = Int.abs m 471 and n = Int.abs n 472 in 473 if m < n then hcf m n else hcf n m 474 end; 475end; 476 477local 478 fun calcPrimes mode ps i n = 479 if n = 0 then ps 480 else if List.exists (fn p => divides p i) ps then 481 let 482 val i = i + 1 483 and n = if mode then n else n - 1 484 in 485 calcPrimes mode ps i n 486 end 487 else 488 let 489 val ps = ps @ [i] 490 and i = i + 1 491 and n = n - 1 492 in 493 calcPrimes mode ps i n 494 end; 495in 496 fun primes n = 497 if n <= 0 then [] 498 else calcPrimes true [2] 3 (n - 1); 499 500 fun primesUpTo n = 501 if n < 2 then [] 502 else calcPrimes false [2] 3 (n - 2); 503end; 504 505(* ------------------------------------------------------------------------- *) 506(* Strings. *) 507(* ------------------------------------------------------------------------- *) 508 509local 510 fun len l = (length l, l) 511 512 val upper = len (String.explode "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 513 514 val lower = len (String.explode "abcdefghijklmnopqrstuvwxyz"); 515 516 fun rotate (n,l) c k = 517 List.nth (l, (k + Option.valOf (index (equal c) l)) mod n); 518in 519 fun rot k c = 520 if Char.isLower c then rotate lower c k 521 else if Char.isUpper c then rotate upper c k 522 else c; 523end; 524 525fun charToInt #"0" = SOME 0 526 | charToInt #"1" = SOME 1 527 | charToInt #"2" = SOME 2 528 | charToInt #"3" = SOME 3 529 | charToInt #"4" = SOME 4 530 | charToInt #"5" = SOME 5 531 | charToInt #"6" = SOME 6 532 | charToInt #"7" = SOME 7 533 | charToInt #"8" = SOME 8 534 | charToInt #"9" = SOME 9 535 | charToInt _ = NONE; 536 537fun charFromInt 0 = SOME #"0" 538 | charFromInt 1 = SOME #"1" 539 | charFromInt 2 = SOME #"2" 540 | charFromInt 3 = SOME #"3" 541 | charFromInt 4 = SOME #"4" 542 | charFromInt 5 = SOME #"5" 543 | charFromInt 6 = SOME #"6" 544 | charFromInt 7 = SOME #"7" 545 | charFromInt 8 = SOME #"8" 546 | charFromInt 9 = SOME #"9" 547 | charFromInt _ = NONE; 548 549fun nChars x = 550 let 551 fun dup 0 l = l | dup n l = dup (n - 1) (x :: l) 552 in 553 fn n => String.implode (dup n []) 554 end; 555 556fun chomp s = 557 let 558 val n = size s 559 in 560 if n = 0 orelse String.sub (s, n - 1) <> #"\n" then s 561 else String.substring (s, 0, n - 1) 562 end; 563 564local 565 fun chop l = 566 case l of 567 [] => [] 568 | h :: t => if Char.isSpace h then chop t else l; 569in 570 val trim = String.implode o chop o List.rev o chop o List.rev o String.explode; 571end; 572 573val join = String.concatWith; 574 575local 576 fun match [] l = SOME l 577 | match _ [] = NONE 578 | match (x :: xs) (y :: ys) = if x = y then match xs ys else NONE; 579 580 fun stringify acc [] = acc 581 | stringify acc (h :: t) = stringify (String.implode h :: acc) t; 582in 583 fun split sep = 584 let 585 val pat = String.explode sep 586 587 fun div1 prev recent [] = stringify [] (List.rev recent :: prev) 588 | div1 prev recent (l as h :: t) = 589 case match pat l of 590 NONE => div1 prev (h :: recent) t 591 | SOME rest => div1 (List.rev recent :: prev) [] rest 592 in 593 fn s => div1 [] [] (String.explode s) 594 end; 595end; 596 597fun capitalize s = 598 if s = "" then s 599 else str (Char.toUpper (String.sub (s,0))) ^ String.extract (s,1,NONE); 600 601fun mkPrefix p s = p ^ s; 602 603fun destPrefix p = 604 let 605 fun check s = 606 if String.isPrefix p s then () 607 else raise Error "destPrefix" 608 609 val sizeP = size p 610 in 611 fn s => 612 let 613 val () = check s 614 in 615 String.extract (s,sizeP,NONE) 616 end 617 end; 618 619fun isPrefix p = can (destPrefix p); 620 621fun stripPrefix pred s = 622 Substring.string (Substring.dropl pred (Substring.full s)); 623 624fun mkSuffix p s = s ^ p; 625 626fun destSuffix p = 627 let 628 fun check s = 629 if String.isSuffix p s then () 630 else raise Error "destSuffix" 631 632 val sizeP = size p 633 in 634 fn s => 635 let 636 val () = check s 637 638 val sizeS = size s 639 in 640 String.substring (s, 0, sizeS - sizeP) 641 end 642 end; 643 644fun isSuffix p = can (destSuffix p); 645 646fun stripSuffix pred s = 647 Substring.string (Substring.dropr pred (Substring.full s)); 648 649(* ------------------------------------------------------------------------- *) 650(* Tables. *) 651(* ------------------------------------------------------------------------- *) 652 653type columnAlignment = {leftAlign : bool, padChar : char} 654 655fun alignColumn {leftAlign,padChar} column = 656 let 657 val (n,_) = maximum Int.compare (List.map size column) 658 659 fun pad entry row = 660 let 661 val padding = nChars padChar (n - size entry) 662 in 663 if leftAlign then entry ^ padding ^ row 664 else padding ^ entry ^ row 665 end 666 in 667 zipWith pad column 668 end; 669 670local 671 fun alignTab aligns rows = 672 case aligns of 673 [] => List.map (K "") rows 674 | [{leftAlign = true, padChar = #" "}] => List.map hd rows 675 | align :: aligns => 676 let 677 val col = List.map hd rows 678 and cols = alignTab aligns (List.map tl rows) 679 in 680 alignColumn align col cols 681 end; 682in 683 fun alignTable aligns rows = 684 if List.null rows then [] else alignTab aligns rows; 685end; 686 687(* ------------------------------------------------------------------------- *) 688(* Reals. *) 689(* ------------------------------------------------------------------------- *) 690 691val realToString = Real.toString; 692 693fun percentToString x = Int.toString (Real.round (100.0 * x)) ^ "%"; 694 695fun pos r = Real.max (r,0.0); 696 697local 698 val invLn2 = 1.0 / Math.ln 2.0; 699in 700 fun log2 x = invLn2 * Math.ln x; 701end; 702 703(* ------------------------------------------------------------------------- *) 704(* Sums. *) 705(* ------------------------------------------------------------------------- *) 706 707datatype ('a,'b) sum = Left of 'a | Right of 'b 708 709fun destLeft (Left l) = l 710 | destLeft _ = raise Error "destLeft"; 711 712fun isLeft (Left _) = true 713 | isLeft (Right _) = false; 714 715fun destRight (Right r) = r 716 | destRight _ = raise Error "destRight"; 717 718fun isRight (Left _) = false 719 | isRight (Right _) = true; 720 721(* ------------------------------------------------------------------------- *) 722(* Useful impure features. *) 723(* ------------------------------------------------------------------------- *) 724 725local 726 val generator = ref 0 727 728 fun newIntThunk () = 729 let 730 val n = !generator 731 732 val () = generator := n + 1 733 in 734 n 735 end; 736 737 fun newIntsThunk k () = 738 let 739 val n = !generator 740 741 val () = generator := n + k 742 in 743 interval n k 744 end; 745in 746 fun newInt () = Portable.critical newIntThunk (); 747 748 fun newInts k = 749 if k <= 0 then [] 750 else Portable.critical (newIntsThunk k) (); 751end; 752 753fun withRef (r,new) f x = 754 let 755 val old = !r 756 val () = r := new 757 val y = f x handle e => (r := old; raise e) 758 val () = r := old 759 in 760 y 761 end; 762 763fun cloneArray a = 764 let 765 fun index i = Array.sub (a,i) 766 in 767 Array.tabulate (Array.length a, index) 768 end; 769 770(* ------------------------------------------------------------------------- *) 771(* Environment. *) 772(* ------------------------------------------------------------------------- *) 773 774fun host () = Option.getOpt (OS.Process.getEnv "HOSTNAME", "unknown"); 775 776fun time () = Date.fmt "%H:%M:%S" (Date.fromTimeLocal (Time.now ())); 777 778fun date () = Date.fmt "%d/%m/%Y" (Date.fromTimeLocal (Time.now ())); 779 780fun readDirectory {directory = dir} = 781 let 782 val dirStrm = OS.FileSys.openDir dir 783 784 fun readAll acc = 785 case OS.FileSys.readDir dirStrm of 786 NONE => acc 787 | SOME file => 788 let 789 val filename = OS.Path.joinDirFile {dir = dir, file = file} 790 791 val acc = {filename = filename} :: acc 792 in 793 readAll acc 794 end 795 796 val filenames = readAll [] 797 798 val () = OS.FileSys.closeDir dirStrm 799 in 800 List.rev filenames 801 end; 802 803fun readTextFile {filename} = 804 let 805 open TextIO 806 807 val h = openIn filename 808 809 val contents = inputAll h 810 811 val () = closeIn h 812 in 813 contents 814 end; 815 816fun writeTextFile {contents,filename} = 817 let 818 open TextIO 819 val h = openOut filename 820 val () = output (h,contents) 821 val () = closeOut h 822 in 823 () 824 end; 825 826(* ------------------------------------------------------------------------- *) 827(* Profiling and error reporting. *) 828(* ------------------------------------------------------------------------- *) 829 830fun chat s = TextIO.output (TextIO.stdOut, s ^ "\n"); 831 832fun chide s = TextIO.output (TextIO.stdErr, s ^ "\n"); 833 834local 835 fun err x s = chide (x ^ ": " ^ s); 836in 837 fun try f x = f x 838 handle e as Error _ => (err "try" (errorToString e); raise e) 839 | e as Bug _ => (err "try" (bugToString e); raise e) 840 | e => (err "try" "strange exception raised"; raise e); 841 842 val warn = err "WARNING"; 843 844 fun die s = (err "\nFATAL ERROR" s; OS.Process.exit OS.Process.failure); 845end; 846 847fun timed f a = 848 let 849 val tmr = Timer.startCPUTimer () 850 val res = f a 851 val {usr,sys,...} = Timer.checkCPUTimer tmr 852 in 853 (Time.toReal usr + Time.toReal sys, res) 854 end; 855 856local 857 val MIN = 1.0; 858 859 fun several n t f a = 860 let 861 val (t',res) = timed f a 862 val t = t + t' 863 val n = n + 1 864 in 865 if t > MIN then (t / Real.fromInt n, res) else several n t f a 866 end; 867in 868 fun timedMany f a = several 0 0.0 f a 869end; 870 871val executionTime = 872 let 873 val startTime = Time.toReal (Time.now ()) 874 in 875 fn () => Time.toReal (Time.now ()) - startTime 876 end; 877 878end 879