disco 0.1.3.1 → 0.1.4
raw patch · 12 files changed
+134/−33 lines, 12 filesdep ~polysemy-zooPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: polysemy-zoo
API changes (from Hackage documentation)
+ Disco.Module: [_miNames] :: ModuleInfo -> [QName Term]
+ Disco.Module: miNames :: Lens' ModuleInfo [QName Term]
- Disco.Effects.Counter: next :: forall r_ajvR. Member Counter r_ajvR => Sem r_ajvR Integer
+ Disco.Effects.Counter: next :: forall r_ajvU. Member Counter r_ajvU => Sem r_ajvU Integer
- Disco.Effects.Fresh: fresh :: forall r_a1KNv x_X1KMI. Member Fresh r_a1KNv => Name x_X1KMI -> Sem r_a1KNv (Name x_X1KMI)
+ Disco.Effects.Fresh: fresh :: forall r_a1KNw x_X1KMJ. Member Fresh r_a1KNw => Name x_X1KMJ -> Sem r_a1KNw (Name x_X1KMJ)
- Disco.Effects.LFresh: avoid :: forall r_alvG a_alr4. Member LFresh r_alvG => [AnyName] -> Sem r_alvG a_alr4 -> Sem r_alvG a_alr4
+ Disco.Effects.LFresh: avoid :: forall r_alvJ a_alr7. Member LFresh r_alvJ => [AnyName] -> Sem r_alvJ a_alr7 -> Sem r_alvJ a_alr7
- Disco.Effects.LFresh: getAvoids :: forall r_alvJ. Member LFresh r_alvJ => Sem r_alvJ (Set AnyName)
+ Disco.Effects.LFresh: getAvoids :: forall r_alvM. Member LFresh r_alvM => Sem r_alvM (Set AnyName)
- Disco.Effects.LFresh: lfresh :: forall r_alvE a_Xlr2. (Member LFresh r_alvE, Typeable a_Xlr2) => Name a_Xlr2 -> Sem r_alvE (Name a_Xlr2)
+ Disco.Effects.LFresh: lfresh :: forall r_alvH a_Xlr5. (Member LFresh r_alvH, Typeable a_Xlr5) => Name a_Xlr5 -> Sem r_alvH (Name a_Xlr5)
- Disco.Effects.Store: assocsStore :: forall v_aomX r_aotm. Member (Store v_aomX) r_aotm => Sem r_aotm [(Int, v_aomX)]
+ Disco.Effects.Store: assocsStore :: forall v_aomY r_aotn. Member (Store v_aomY) r_aotn => Sem r_aotn [(Int, v_aomY)]
- Disco.Effects.Store: clearStore :: forall v_aomN r_aotc. Member (Store v_aomN) r_aotc => Sem r_aotc ()
+ Disco.Effects.Store: clearStore :: forall v_aomO r_aotd. Member (Store v_aomO) r_aotd => Sem r_aotd ()
- Disco.Effects.Store: insertStore :: forall v_aomT r_aoth. Member (Store v_aomT) r_aoth => Int -> v_aomT -> Sem r_aoth ()
+ Disco.Effects.Store: insertStore :: forall v_aomU r_aoti. Member (Store v_aomU) r_aoti => Int -> v_aomU -> Sem r_aoti ()
- Disco.Effects.Store: keepKeys :: forall v_aomZ r_aotn. Member (Store v_aomZ) r_aotn => IntSet -> Sem r_aotn ()
+ Disco.Effects.Store: keepKeys :: forall v_aon0 r_aoto. Member (Store v_aon0) r_aoto => IntSet -> Sem r_aoto ()
- Disco.Effects.Store: lookupStore :: forall v_aomR r_aotf. Member (Store v_aomR) r_aotf => Int -> Sem r_aotf (Maybe v_aomR)
+ Disco.Effects.Store: lookupStore :: forall v_aomS r_aotg. Member (Store v_aomS) r_aotg => Int -> Sem r_aotg (Maybe v_aomS)
- Disco.Effects.Store: mapStore :: forall v_aomV r_aotk. Member (Store v_aomV) r_aotk => (v_aomV -> v_aomV) -> Sem r_aotk ()
+ Disco.Effects.Store: mapStore :: forall v_aomW r_aotl. Member (Store v_aomW) r_aotl => (v_aomW -> v_aomW) -> Sem r_aotl ()
- Disco.Effects.Store: new :: forall v_aomP r_aotd. Member (Store v_aomP) r_aotd => v_aomP -> Sem r_aotd Int
+ Disco.Effects.Store: new :: forall v_aomQ r_aote. Member (Store v_aomQ) r_aote => v_aomQ -> Sem r_aote Int
- Disco.Module: ModuleInfo :: ModuleName -> Map ModuleName ModuleInfo -> Ctx Term Docs -> Ctx ATerm [AProperty] -> TyCtx -> TyDefCtx -> Ctx ATerm Defn -> [(ATerm, PolyType)] -> ExtSet -> ModuleInfo
+ Disco.Module: ModuleInfo :: ModuleName -> Map ModuleName ModuleInfo -> [QName Term] -> Ctx Term Docs -> Ctx ATerm [AProperty] -> TyCtx -> TyDefCtx -> Ctx ATerm Defn -> [(ATerm, PolyType)] -> ExtSet -> ModuleInfo
Files
- CHANGELOG.md +13/−0
- disco.cabal +2/−2
- docs/tutorial/example/case-pattern.disco +1/−1
- example/grid.disco +45/−8
- src/Disco/Interactive/Commands.hs +7/−6
- src/Disco/Interpret/CESK.hs +1/−3
- src/Disco/Module.hs +7/−3
- src/Disco/Parser.hs +5/−4
- src/Disco/Typecheck.hs +46/−4
- src/Disco/Typecheck/Solve.hs +5/−0
- test/case-basic/case-basic.disco +1/−1
- test/types-tydef-qual/expected +1/−1
CHANGELOG.md view
@@ -1,3 +1,16 @@+* 0.1.4 (10 March 2022)++ - New features or syntax+ - `if` and `when` are now synonyms++ - Bug fixes+ - Only allow constructing sets and bags with element types+ that can be compared ([#306](https://github.com/disco-lang/disco/issues/306))++ - UI+ - Test results are now printed in the same order in which+ they were declared+ * 0.1.3.1 (5 March 2022) - Fix [#340](https://github.com/disco-lang/disco/issues/340): disco
disco.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: disco-version: 0.1.3.1+version: 0.1.4 synopsis: Functional programming language for teaching discrete math. description: Disco is a simple functional programming language for use in teaching discrete math. Its syntax is designed to be close@@ -455,7 +455,7 @@ unbound-generics >= 0.3 && < 0.5, polysemy >= 1.6.0.0 && < 1.8, polysemy-plugin >= 0.4 && < 0.5,- polysemy-zoo >= 0.7 && < 0.8,+ polysemy-zoo >= 0.7 && < 0.9, lens >= 4.14 && < 5.2, exact-combinatorics >= 0.2 && < 0.3, arithmoi >= 0.10 && < 0.13,
docs/tutorial/example/case-pattern.disco view
@@ -1,5 +1,5 @@ g : Z*Z -> Z g(p) = {? 0 when p is (3,_),- x + y when p is (x,y) if x > 5 or y > 20,+ x + y when p is (x,y) when x > 5 or y > 20, -100 otherwise ?}
example/grid.disco view
@@ -37,6 +37,7 @@ ||| where the first column contains the triangular numbers. !!! ∀ n : Nat. diagIso (diagIso' n) == n+!!! ∀ p : Nat * Nat. diagIso' (diagIso p) == p diagIso : ℕ×ℕ → ℕ diagIso (x,y) = (x+y)*(x+y+1)//2 + x@@ -54,22 +55,58 @@ ||| and ℕ×ℕ, but at the moment the disco type system doesn't let us ||| say that (and it likely never will). --- We have to be careful not to call powerIso' 0 because it gets stuck+-- We have to be careful not to call powerIsoPos' 0 because it gets stuck -- in infinite recursion! One way that would work is to write the -- test as follows: -!!! forall n:Nat. powerIso (powerIso' (n+1)) == (n+1)+!!! forall n:Nat. powerIsoPos (powerIsoPos' (n+1)) == (n+1) -- Alternatively, since 'implies' is lazy (i.e. "short-circuiting"), -- we can write -!!! ∀ n : Nat. (n > 0) ==> powerIso (powerIso' n) == n+!!! ∀ n : Nat. (n > 0) ==> powerIsoPos (powerIsoPos' n) == n -powerIso : ℕ×ℕ → ℕ-powerIso (x,y) = 2^x * (2y + 1)+powerIsoPos : ℕ×ℕ → ℕ+powerIsoPos (x,y) = 2^x * (2y + 1) -powerIso' : ℕ → ℕ×ℕ-powerIso' n =+powerIsoPos' : ℕ → ℕ×ℕ+powerIsoPos' n = {? (0, n//2) if not (2 divides n),- (x+1,y) when powerIso' (n//2) is (x,y)+ (x+1,y) when powerIsoPos' (n//2) is (x,y) ?}++||| We can turn powerIsoPos into an isomorphism between all natural+||| numbers and ℕ×ℕ simply by adding and subtracting one.++!!! forall n:Nat. powerIso (powerIso' n) == n+!!! ∀ p : ℕ×ℕ. powerIso' (powerIso p) == p++powerIso : ℕ×ℕ → ℕ+powerIso(p) = powerIsoPos(p) .- 1++powerIso' : ℕ → ℕ×ℕ+powerIso'(n) = powerIsoPos'(n+1)+++||| And finally, there is a very cool isomorphism called "Morton+||| Z-order" which takes a pair of natural numbers, expresses them in+||| binary, and interleaves their binary representations to form a+||| single natural number. It ends up looking like this:+|||+||| 0 2 8 10+||| 1 3 9 11+||| 4 6 12 14+||| 5 7 13 15++!!! forall n:N. zOrder(zOrder'(n)) == n+!!! forall p:N*N. zOrder'(zOrder(p)) == p++zOrder : ℕ×ℕ → ℕ+zOrder(0,0) = 0+zOrder(2m,n) = 2 * zOrder(n,m)+zOrder(2m+1,n) = 2 * zOrder(n,m) + 1++zOrder' : ℕ → ℕ×ℕ+zOrder'(0) = (0,0)+zOrder'(2n) = {? (2y,x) when zOrder'(n) is (x,y) ?}+zOrder'(2n+1) = {? (2y+1,x) when zOrder'(n) is (x,y) ?}
src/Disco/Interactive/Commands.hs view
@@ -48,7 +48,7 @@ import Polysemy.Output import Polysemy.Reader -import Data.Maybe (maybeToList)+import Data.Maybe (mapMaybe, maybeToList) import Disco.AST.Surface import Disco.AST.Typed import Disco.Compile@@ -589,7 +589,7 @@ setREPLModule m -- Now run any tests- t <- inputToState $ runAllTests (m ^. miProps)+ t <- inputToState $ runAllTests (m ^. miNames) (m ^. miProps) -- Evaluate and print any top-level terms forM_ (m ^. miTerms) (mapError EvalErr . evalTerm True . fst)@@ -602,18 +602,19 @@ -- XXX Return a structured summary of the results, not a Bool; -- separate out results generation and pretty-printing, & move this -- somewhere else.-runAllTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => Ctx ATerm [AProperty] -> Sem r Bool -- (Ctx ATerm [TestResult])-runAllTests aprops+runAllTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => [QName Term] -> Ctx ATerm [AProperty] -> Sem r Bool -- (Ctx ATerm [TestResult])+runAllTests declNames aprops | Ctx.null aprops = return True | otherwise = do info "Running tests..."- and <$> mapM (uncurry runTests) (Ctx.assocs aprops)+ -- Use the order the names were defined in the module+ and <$> mapM (uncurry runTests) (mapMaybe (\n -> (n,) <$> Ctx.lookup' (coerce n) aprops) declNames) where numSamples :: Int numSamples = 50 -- XXX make this configurable somehow - runTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => QName ATerm -> [AProperty] -> Sem r Bool+ runTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => QName Term -> [AProperty] -> Sem r Bool runTests (QName _ n) props = do results <- inputTopEnv $ traverse (sequenceA . (id &&& runTest numSamples)) props let failures = P.filter (not . testIsOk . snd) results
src/Disco/Interpret/CESK.hs view
@@ -21,8 +21,6 @@ ) where -import Text.Show.Pretty (ppShow)- import Unbound.Generics.LocallyNameless (Bind, Name) import Algebra.Graph@@ -547,7 +545,7 @@ valCmp (VBag cs1) (VBag cs2) = compareBags cs1 cs2 valCmp (VMap m1) (VMap m2) = compareMaps (M.assocs m1) (M.assocs m2) valCmp (VGraph g1) (VGraph g2) = valCmp (graphSummary g1) (graphSummary g2)-valCmp v1 v2 = error $ "valCmp " ++ show v1 ++ " " ++ show v2+valCmp v1 v2 = error $ "valCmp\n " ++ take 100 (show v1) ++ "...\n " ++ take 100 (show v2) ++ "..." compareBags :: [(Value, Integer)] -> [(Value, Integer)] -> Ordering compareBags [] [] = EQ
src/Disco/Module.hs view
@@ -97,6 +97,9 @@ data ModuleInfo = ModuleInfo { _miName :: ModuleName , _miImports :: Map ModuleName ModuleInfo++ -- List of names declared by the module, in the order they occur+ , _miNames :: [QName Term] , _miDocs :: Ctx Term Docs , _miProps :: Ctx ATerm [AProperty] , _miTys :: TyCtx@@ -117,11 +120,12 @@ -- taken from the first. Definitions from later modules override -- earlier ones. Note that this function should really only be used -- for the special top-level REPL module.- ModuleInfo n1 is1 d1 _ ty1 tyd1 tm1 tms1 es1- <> ModuleInfo _ is2 d2 p2 ty2 tyd2 tm2 tms2 es2+ ModuleInfo n1 is1 ns1 d1 _ ty1 tyd1 tm1 tms1 es1+ <> ModuleInfo _ is2 ns2 d2 p2 ty2 tyd2 tm2 tms2 es2 = ModuleInfo n1 (is1 <> is2)+ (ns1 <> ns2) (d2 <> d1) p2 (ty2 <> ty1)@@ -148,7 +152,7 @@ -- | The empty module info record. emptyModuleInfo :: ModuleInfo-emptyModuleInfo = ModuleInfo REPLModule M.empty emptyCtx emptyCtx emptyCtx M.empty emptyCtx [] S.empty+emptyModuleInfo = ModuleInfo REPLModule M.empty [] emptyCtx emptyCtx emptyCtx M.empty emptyCtx [] S.empty ------------------------------------------------------------ -- Module resolution
src/Disco/Parser.hs view
@@ -775,12 +775,13 @@ parseGuards :: Parser (Telescope Guard) parseGuards = (TelEmpty <$ reserved "otherwise") <|> (toTelescope <$> many parseGuard) --- | Parse a single guard (either @if@ or @when@)+-- | Parse a single guard (@if@, @if ... is@, or @let@) parseGuard :: Parser Guard-parseGuard = parseGBool <|> parseGPat <|> parseGLet+parseGuard = try parseGPat <|> parseGBool <|> parseGLet where- parseGBool = GBool <$> (embed <$> (reserved "if" *> parseTerm))- parseGPat = GPat <$> (embed <$> (reserved "when" *> parseTerm))+ guardWord = reserved "if" <|> reserved "when"+ parseGBool = GBool <$> (embed <$> (guardWord *> parseTerm))+ parseGPat = GPat <$> (embed <$> (guardWord *> parseTerm)) <*> (reserved "is" *> parsePattern) parseGLet = GLet <$> (reserved "let" *> parseBinding)
src/Disco/Typecheck.hs view
@@ -128,10 +128,13 @@ [] -> do aprops <- mapError noLoc $ checkProperties docCtx -- XXX location? aterms <- mapError noLoc $ mapM inferTop terms -- XXX location?- return $ ModuleInfo name imports docCtx aprops tyCtx tyDefnCtx defnCtx aterms es+ return $ ModuleInfo name imports (map ((name .-) . getDeclName) typeDecls) docCtx aprops tyCtx tyDefnCtx defnCtx aterms es where getDefnName :: Defn -> Name ATerm getDefnName (Defn n _ _ _) = n + getDeclName :: TypeDecl -> Name Term+ getDeclName (TypeDecl n _) = n+ -------------------------------------------------- -- Type definitions @@ -1134,9 +1137,11 @@ constraints $ map (`CSub` tyv) tys return $ containerTy c tyv Check ty -> return ty- when (isJust ell) $ do- eltTy <- getEltTy c resTy- constraint $ CQual QEnum eltTy+ eltTy <- getEltTy c resTy++ -- See Note [Container literal constraints]+ when (c /= ListContainer && not (P.null xs)) $ constraint $ CQual QCmp eltTy+ when (isJust ell) $ constraint $ CQual QEnum eltTy return $ ATContainer resTy c axns aell where@@ -1145,6 +1150,43 @@ => Mode -> Maybe (Ellipsis Term) -> Sem r (Maybe (Ellipsis ATerm)) typecheckEllipsis _ Nothing = return Nothing typecheckEllipsis m (Just (Until tm)) = Just . Until <$> typecheck m tm++-- ~~~~ Note [Container literal constraints]+--+-- It should only be possible to construct something of type Set(a) or+-- Bag(a) when a is comparable, so we can normalize the set or bag+-- value. For example, Set(N) is OK, but Set(N -> N) is not. On the+-- other hand, List(a) is fine for any type a. We want to maintain+-- the invariant that we can only actually obtain a value of type+-- Set(a) or Bag(a) if a is comparable. This means we will be able to+-- write polymorphic functions that take bags or sets as input without+-- having to specify any constraints --- the only way to call such+-- functions is with element types that actually support comparison.+-- For example, 'unions' can simply have the type Set(Set(a)) ->+-- Set(a).+--+-- Hence, container literals (along with the 'set' and 'bag'+-- conversion functions) serve as "gatekeepers" to make sure we can+-- only construct containers with appropriate element types. So when+-- we see a container literal, if it is a bag or set literal, we have+-- to introduce an additional QCmp constraint for the element type.+--+-- But not so fast --- with that rule, 'unions' does not type check!+-- To see why, look at the definition:+--+-- unions(ss) = foldr(~∪~, {}, list(ss))+--+-- The empty set literal in the definition means we end up generating+-- a QCmp constraint on the element type anyway. But there is a+-- solution: we refine our invariant to say that we can only+-- actually obtain a *non-empty* value of type Set(a) or Bag(a) if a+-- is comparable. Empty bags and sets are allowed to have any element+-- type. This is safe because there is no way to generate a non-empty+-- set from an empty one, without also making use of something like a+-- non-empty set literal or conversion function. So we add a special+-- case to the rule that says we only add a QCmp constraint in the+-- case of a *non-empty* set or bag literal. Now the definition of+-- 'unions' type checks perfectly well. -- Container comprehensions typecheck mode tcc@(TContainerComp c bqt) = do
src/Disco/Typecheck/Solve.hs view
@@ -421,6 +421,11 @@ let ty' = body tys go (S.insert (t, tys, q) seen) ty' q + -- If we have a container type where the container is still a variable,+ -- just replace it with List for the purposes of generating constraints---+ -- all containers (lists, bags, sets) have the same qualifier rules.+ go seen (TyCon (CContainer (AVar _)) tys) q = go seen (TyCon CList tys) q+ -- Otherwise, decompose a type constructor according to the qualRules. go seen ty@(TyCon c tys) q = case qualRules c q of Nothing -> throw $ Unqual q ty
test/case-basic/case-basic.disco view
@@ -1,7 +1,7 @@ foo : List(N) + Bool -> N foo x = {? n when x is left (n :: _),- 0 when x is left [],+ 0 if x is left [], 1 when x is right True, 2 when x is right False ?}
test/types-tydef-qual/expected view
@@ -1,5 +1,5 @@ Loading tydef-qual.disco... Running tests...- mirror: OK x: OK+ mirror: OK Loaded.