1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Optimize.DecisionTree
12{- To learn more about how this works, definitely read through:
14 "When Do Match-Compilation Heuristics Matter?"
16by Kevin Scott and Norman Ramsey. The rough idea is that we start with a simple
17list of patterns and expressions, and then turn that into a "decision tree"
18that requires as few tests as possible to make it to a leaf. Read the paper, it
19explains this extraordinarily well! We are currently using the same heuristics
20as SML/NJ to get nice trees.
23import Control.Arrow (second)
24import Control.Monad (liftM, liftM2)
26import qualified Data.List as List
27import qualified Data.Maybe as Maybe
28import qualified Data.Set as Set
29import Data.Text (Text)
31import qualified AST.Canonical as Can
32import qualified Elm.Name as N
33import qualified Reporting.Annotation as A
40{-| Users of this module will mainly interact with this function. It takes
41some normal branches and gives out a decision tree that has "labels" at all
42the leafs and a dictionary that maps these "labels" to the code that should
45If 2 or more leaves point to the same label, we need to do some tricks in JS to
46make that work nicely. When is JS getting goto?! ;) That is outside the scope
49compile :: [(Can.Pattern, Int)] -> DecisionTree
52 format (pattern, index) =
53 Branch index [(Empty, pattern)]
55 toDecisionTree (map format rawBranches)
66 , _edges :: [(Test, DecisionTree)]
67 , _default :: Maybe DecisionTree
92add :: Path -> Path -> Path
99 error "nothing should be added to an alias path"
101 Index index subpath ->
102 Index index (add subpath finalLink)
104 Field name subpath ->
105 Field name (add subpath finalLink)
108subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
109subPositions path patterns =
111 (\index pattern -> (add path (Index index Empty), pattern))
117-- ACTUALLY BUILD DECISION TREES
123 , _patterns :: [(Path, Can.Pattern)]
127toDecisionTree :: [Branch] -> DecisionTree
128toDecisionTree rawBranches =
131 map flattenPatterns rawBranches
133 case checkForMatch branches of
143 gatherEdges branches path
146 map (second toDecisionTree) edges
148 case (decisionEdges, fallback) of
149 ([(_tag, decisionTree)], []) ->
153 Decision path decisionEdges Nothing
156 toDecisionTree fallback
159 Decision path decisionEdges (Just (toDecisionTree fallback))
162isComplete :: [Test] -> Bool
176{-| Flatten type aliases and use the VariantDict to figure out when a tag is
177the only variant so we can skip doing any tests on it.
179flattenPatterns :: Branch -> Branch
180flattenPatterns (Branch goal pathPatterns) =
181 Branch goal (concatMap flatten pathPatterns)
184flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)]
185flatten pathPattern@(path, A.At region pattern) =
193 Can.PAlias realPattern alias ->
194 (add path Alias, A.At region (Can.PVar alias))
196 flatten (path, realPattern)
204 Can.PTuple a b Nothing ->
205 concatMap flatten (subPositions path [a,b])
207 Can.PTuple a b (Just c) ->
208 concatMap flatten (subPositions path [a,b,c])
210 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) _ args ->
212 concatMap flatten (subPositions path (map dearg args))
232dearg :: Can.PatternCtorArg -> Can.Pattern
233dearg (Can.PatternCtorArg _ _ pattern) =
241{-| If the first branch has no more "decision points" we can finally take that
242path. If that is the case we give the resulting label and a mapping from free
243variables to "how to get their value". So a pattern like (Just (x,_)) will give
244us something like ("x" => value.0.0)
246checkForMatch :: [Branch] -> Maybe Int
247checkForMatch branches =
249 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
257-- GATHER OUTGOING EDGES
260gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
261gatherEdges branches path =
264 testsAtPath path branches
267 map (edgesFor path branches) relevantTests
270 if isComplete relevantTests then
273 filter (isIrrelevantTo path) branches
275 ( allEdges, fallbacks )
279-- FIND RELEVANT TESTS
282testsAtPath :: Path -> [Branch] -> [Test]
283testsAtPath selectedPath branches =
286 Maybe.mapMaybe (testAtPath selectedPath) branches
288 skipVisited test curr@(uniqueTests, visitedTests) =
289 if Set.member test visitedTests then
293 , Set.insert test visitedTests
296 fst (foldr skipVisited ([], Set.empty) allTests)
299testAtPath :: Path -> Branch -> Maybe Test
300testAtPath selectedPath (Branch _ pathPatterns) =
301 case List.lookup selectedPath pathPatterns of
305 Just (A.At _ pattern) ->
307 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) name _ ->
308 Just (IsCtor numAlts name)
332 error "aliases should never reach 'testAtPath' function"
347{-# NOINLINE nilTest #-}
349nilTest = IsCtor 2 "[]"
352{-# NOINLINE consTest #-}
354consTest = IsCtor 2 "::"
361edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
362edgesFor path branches test =
364 , Maybe.mapMaybe (toRelevantBranch test path) branches
368toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
369toRelevantBranch test path branch@(Branch goal pathPatterns) =
370 case extract path pathPatterns of
371 Just (start, A.At region pattern, end) ->
373 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) name args ->
374 if test == IsCtor numAlts name then
375 Just (Branch goal (start ++ subPositions path (map dearg args) ++ end))
380 if test == IsCtor 2 "[]" then
381 Just (Branch goal (start ++ end))
386 if test == IsCtor 2 "::" then
387 let tl' = A.At region (Can.PList tl) in
388 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
393 if test == IsCtor 2 "::" then
394 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
399 if test == IsChr chr then
400 Just (Branch goal (start ++ end))
405 if test == IsStr str then
406 Just (Branch goal (start ++ end))
411 if test == IsInt int then
412 Just (Branch goal (start ++ end))
425 -> [(Path, Can.Pattern)]
426 -> Maybe ([(Path, Can.Pattern)], Can.Pattern, [(Path, Can.Pattern)])
427extract selectedPath pathPatterns =
432 first@(path, pattern) : rest ->
433 if path == selectedPath then
434 Just ([], pattern, rest)
437 case extract selectedPath rest of
441 Just (start, foundPattern, end) ->
442 Just (first : start, foundPattern, end)
446-- FIND IRRELEVANT BRANCHES
449isIrrelevantTo :: Path -> Branch -> Bool
450isIrrelevantTo selectedPath (Branch _ pathPatterns) =
451 case List.lookup selectedPath pathPatterns of
456 not (needsTests pattern)
459needsTests :: Can.Pattern -> Bool
460needsTests (A.At _ pattern) =
469 error "aliases should never reach 'isIrrelevantTo' function"
474 Can.PCtor _ _ _ _ _ _ ->
503pickPath :: [Branch] -> Path
507 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
509 case bests (addWeights (smallDefaults branches) allPaths) of
514 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
517isChoicePath :: (Path, Can.Pattern) -> Maybe Path
518isChoicePath (path, pattern) =
519 if needsTests pattern then
525addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
526addWeights toWeight paths =
527 map (\path -> (path, toWeight path)) paths
530bests :: [(Path, Int)] -> [Path]
534 error "Cannot choose the best of zero paths. This should never happen."
536 (headPath, headWeight) : weightedPaths ->
538 gatherMinimum acc@(minWeight, paths) (path, weight) =
539 if weight == minWeight then
540 (minWeight, path : paths)
542 else if weight < minWeight then
548 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
552-- PATH PICKING HEURISTICS
555smallDefaults :: [Branch] -> Path -> Int
556smallDefaults branches path =
557 length (filter (isIrrelevantTo path) branches)
560smallBranchingFactor :: [Branch] -> Path -> Int
561smallBranchingFactor branches path =
564 gatherEdges branches path
566 length edges + (if null fallback then 0 else 1)
573instance Binary Test where
576 IsCtor a b -> putWord8 0 >> put a >> put b
577 IsChr a -> putWord8 1 >> put a
578 IsStr a -> putWord8 2 >> put a
579 IsInt a -> putWord8 3 >> put a
584 0 -> liftM2 IsCtor get get
588 _ -> error "problem getting DecisionTree.Test binary"
591instance Binary Path where
594 Index a b -> putWord8 0 >> put a >> put b
595 Field a b -> putWord8 1 >> put a >> put b
602 0 -> liftM2 Index get get
603 1 -> liftM2 Field get get
606 _ -> error "problem getting DecisionTree.Path binary"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list