1{-# OPTIONS_GHC -Wall #-}
2module Optimize.Patterns.DecisionTree where
3{- To learn more about how this works, definitely read through:
5 "When Do Match-Compilation Heuristics Matter?"
7by Kevin Scott and Norman Ramsey. The rough idea is that we start with a simple
8list of patterns and expressions, and then turn that into a "decision tree"
9that requires as few tests as possible to make it to a leaf. Read the paper, it
10explains this extraordinarily well! We are currently using the same heuristics
11as SML/NJ to get nice trees.
14import Control.Arrow (second)
15import Data.Function (on)
16import qualified Data.List as List
17import qualified Data.Map as Map
18import qualified Data.Set as Set
20import qualified Data.Maybe as Maybe
22import qualified AST.Literal as L
23import qualified AST.Pattern as P
24import qualified AST.Variable as Var
25import qualified Reporting.Annotation as A
28type CPattern = P.CanonicalPattern
33{-| Users of this module will mainly interact with this function. It takes
34some normal branches and gives out a decision tree that has "labels" at all
35the leafs and a dictionary that maps these "labels" to the code that should
38If 2 or more leaves point to the same label, we need to do some tricks in JS to
39make that work nicely. When is JS getting goto?! ;) That is outside the scope
42compile :: VariantDict -> [(CPattern, Int)] -> DecisionTree Jump
43compile variantDict rawBranches =
45 format (pattern, index) =
46 Branch index [(Empty, pattern)]
48 toDecisionTree variantDict (map format rawBranches)
51{-| When a certain union type is defined, you specify a certain number of tags.
52This helps us do a few optimizations:
54 * If there is only one possible tag, we can always skip checking what it is.
55 Tuples are a common example of this.
56 * If we use all possible tags, we can skip doing the last test. If we have
57 checked N-1 of N tags, there is no need to test the Nth, we know its the
60So this dictionary maps tags to the number of variants that exist, so it'll
61contain things like [ ("Just", 2), ("Nothing", 2), ("_Tuple2", 1), ... ] which
62we can use for these optimizations.
65 Map.Map Var.Canonical Int
74 , _edges :: [(Test, DecisionTree a)]
75 , _default :: Maybe (DecisionTree a)
81 , _substitutions :: [(String, Path)]
86 = Constructor Var.Canonical
101add :: Path -> Path -> Path
108 error "nothing should be added to an alias path"
110 Position index subpath ->
111 Position index (add subpath finalLink)
113 Field name subpath ->
114 Field name (add subpath finalLink)
117subPositions :: Path -> [CPattern] -> [(Path, CPattern)]
118subPositions path patterns =
120 (\index pattern -> (add path (Position index Empty), pattern))
125-- ACTUALLY BUILD DECISION TREES
130 , _patterns :: [(Path, CPattern)]
134toDecisionTree :: VariantDict -> [Branch] -> DecisionTree Jump
135toDecisionTree variantDict rawBranches =
138 map (flattenPatterns variantDict) rawBranches
140 case checkForMatch branches of
141 Just (goal, substitutions) ->
142 Match (Jump goal substitutions)
147 smallDefaults branches
150 testsAtPath path branches
153 map (edgesFor path branches) relevantTests
155 (rawEdges, unexplored) =
156 if isComplete variantDict relevantTests then
158 , snd (last allRawEdges)
163 , filter (isIrrelevantTo path) branches
167 map (second (toDecisionTree variantDict)) rawEdges
169 case (edges, unexplored) of
170 ([(_tag, decisionTree)], []) ->
174 Decision path edges Nothing
177 Decision path edges (Just (toDecisionTree variantDict unexplored))
180isComplete :: VariantDict -> [Test] -> Bool
181isComplete variantDict tests =
184 (variantDict ! name) == length tests
186 Literal (L.Boolean _) ->
196{-| Flatten type aliases and use the VariantDict to figure out when a tag is
197the only variant so we can skip doing any tests on it.
199flattenPatterns :: VariantDict -> Branch -> Branch
200flattenPatterns variantDict (Branch goal pathPatterns) =
201 Branch goal (concatMap (flatten variantDict) pathPatterns)
204flatten :: VariantDict -> (Path, CPattern) -> [(Path, CPattern)]
205flatten variantDict pathPattern@(path, A.A ann pattern) =
213 P.Alias alias realPattern ->
214 [ (add path Alias, A.A ann (P.Var alias))
215 , (path, realPattern)
221 P.Data tag patterns ->
222 if variantDict ! tag == 1 then
223 concatMap (flatten variantDict) (subPositions path patterns)
234{-| If the first branch has no more "decision points" we can finally take that
235path. If that is the case we give the resulting label and a mapping from free
236variables to "how to get their value". So a pattern like (Just (x,_)) will give
237us something like ("x" => value.0.0)
239checkForMatch :: [Branch] -> Maybe (Int, [(String, Path)])
240checkForMatch branches =
242 Branch goal patterns : _ ->
243 (,) goal `fmap` concat `fmap` mapM getSubstitution patterns
249getSubstitution :: (Path, CPattern) -> Maybe [(String, Path)]
250getSubstitution (path, A.A _ pattern) =
259 error "aliases should never reach 'getSubstitution' function"
262 Just (map (\name -> (name, add path (Field name Empty))) fields)
271-- FIND RELEVANT TESTS
273testsAtPath :: Path -> [Branch] -> [Test]
274testsAtPath selectedPath branches =
277 Maybe.mapMaybe (testAtPath selectedPath) branches
279 skipVisited test curr@(uniqueTests, visitedTests) =
280 if Set.member test visitedTests then
284 , Set.insert test visitedTests
287 fst (foldr skipVisited ([], Set.empty) allTests)
290testAtPath :: Path -> Branch -> Maybe Test
291testAtPath selectedPath (Branch _ pathPatterns) =
292 case List.lookup selectedPath pathPatterns of
296 Just (A.A _ pattern) ->
299 Just (Constructor name)
308 error "aliases should never reach 'testAtPath' function"
319edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
320edgesFor path branches test =
322 , Maybe.mapMaybe (toRelevantBranch test path) branches
326toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
327toRelevantBranch test path branch@(Branch goal pathPatterns) =
328 case extract path pathPatterns of
329 Just (start, A.A _ pattern, end) ->
331 P.Data name patterns ->
332 if test == Constructor name then
333 Just (Branch goal (start ++ subPositions path patterns ++ end))
339 if test == Literal lit then
340 Just (Branch goal (start ++ end))
354 -> [(Path, CPattern)]
355 -> Maybe ([(Path, CPattern)], CPattern, [(Path, CPattern)])
356extract selectedPath pathPatterns =
361 first@(path, pattern) : rest ->
362 if path == selectedPath then
363 Just ([], pattern, rest)
366 case extract selectedPath rest of
370 Just (start, foundPattern, end) ->
371 Just (first : start, foundPattern, end)
374-- FIND IRRELEVANT BRANCHES
376isIrrelevantTo :: Path -> Branch -> Bool
377isIrrelevantTo selectedPath (Branch _ pathPatterns) =
378 case List.lookup selectedPath pathPatterns of
383 not (needsTests pattern)
386needsTests :: CPattern -> Bool
387needsTests (A.A _ pattern) =
396 error "aliases should never reach 'isIrrelevantTo' function"
408-- PATH PICKING HEURISTICS
410smallDefaults :: [Branch] -> Path
411smallDefaults branches =
414 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
417 map (\path -> (path, length (filter (isIrrelevantTo path) branches))) allPaths
419 fst (List.minimumBy (compare `on` snd) weightedPaths)
422isChoicePath :: (Path, CPattern) -> Maybe Path
423isChoicePath (path, pattern) =
424 if needsTests pattern then
430-- smallBranchingFactor :: [Branch] -> Path
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