├── .gitattributes ├── .gitignore ├── .stylish-haskell.yaml ├── Makefile ├── README.md ├── Setup.hs ├── executable ├── MLTT.hs └── Main.hs ├── library ├── MLTT.hs └── MLTT │ ├── Infer.hs │ ├── Parser.hs │ ├── Types.hs │ └── Types │ ├── Exceptions.hs │ ├── Expr.hs │ └── Variable.hs └── mltt.cabal /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /cabal-dev/ 3 | /.virtualenv/ 4 | /.hpc/ 5 | /.hsenv/ 6 | /.cabal-sandbox/ 7 | cabal.sandbox.config 8 | cabal.config 9 | *.o 10 | *.hi 11 | *.chi 12 | *.chs.h 13 | *.dyn_o 14 | *.dyn_hi 15 | *.prof 16 | *.aux 17 | *.hp 18 | *.tix 19 | default.nix 20 | shell.nix 21 | scratchpad.hs 22 | *~ 23 | [#]*[#] 24 | .\#* 25 | data/ 26 | -------------------------------------------------------------------------------- /.stylish-haskell.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - imports: 3 | align: global 4 | 5 | - language_pragmas: 6 | style: vertical 7 | remove_redundant: true 8 | 9 | - records: 10 | align: group 11 | 12 | - tabs: 13 | spaces: 8 14 | 15 | - trailing_whitespace: {} 16 | 17 | columns: 80 18 | 19 | language_extensions: 20 | - TemplateHaskell 21 | - QuasiQuotes 22 | - MultiParamTypeClasses -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build clean configure haddock install repl run tags 2 | 3 | shell = '$$SHELL' 4 | 5 | all: install configure build haddock tags 6 | 7 | build: 8 | cabal build --jobs 9 | 10 | clean: nix-clean 11 | cabal clean 12 | -rm -f *.tix 13 | if test -d .cabal-sandbox; then cabal sandbox delete; fi 14 | if test -d .hpc; then rm -r .hpc; fi 15 | 16 | configure: 17 | cabal configure 18 | 19 | haddock: configure 20 | cabal haddock --hyperlink-source 21 | 22 | install: 23 | cabal sandbox init 24 | cabal install --jobs --only-dependencies --reorder-goals 25 | 26 | nix-clean: 27 | if test -e default.nix; then rm default.nix; fi 28 | if test -e shell.nix; then rm shell.nix; fi 29 | 30 | nix-init: clean 31 | cabal2nix --shell . > shell.nix; 32 | cabal2nix . > default.nix; 33 | 34 | nix-shell: nix-init 35 | nix-shell --command 'make install && $(shell)' 36 | make clean 37 | 38 | repl: 39 | cabal repl lib:mltt 40 | 41 | run: 42 | cabal run --jobs . 43 | 44 | tags: 45 | hasktags -e . 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mltt 2 | 3 | An implementation of Martin-Löf Type Theory in Haskell. 4 | 5 | ## Update to latest version of Cabal. 6 | ```sh 7 | cabal update 8 | cabal install cabal-install 9 | ``` 10 | 11 | ## Initialize a sandbox and install the package's dependencies. 12 | ```sh 13 | make install 14 | ``` 15 | 16 | ## For Nix users: 17 | ```sh 18 | make nix-shell 19 | ``` 20 | 21 | ## Configure & build the package. 22 | ```sh 23 | make configure 24 | make build 25 | ``` 26 | 27 | ## Test package. 28 | ```sh 29 | make test 30 | ``` 31 | 32 | ## Run executable. 33 | ```sh 34 | make run 35 | ``` 36 | 37 | ## Start REPL. 38 | ```sh 39 | make repl 40 | ``` 41 | 42 | ## Generate documentation. 43 | ```sh 44 | make haddock 45 | ``` 46 | 47 | ## Analyze coverage. 48 | ```sh 49 | make hpc 50 | ``` 51 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | module Setup (main) where 2 | 3 | import Distribution.Simple (defaultMain) 4 | 5 | main :: IO () 6 | main = defaultMain 7 | -------------------------------------------------------------------------------- /executable/MLTT.hs: -------------------------------------------------------------------------------- 1 | module MLTT where 2 | 3 | import MLTT.Infer 4 | import MLTT.Types 5 | 6 | main :: IO () 7 | main = return () 8 | -------------------------------------------------------------------------------- /executable/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import qualified MLTT 4 | 5 | main :: IO () 6 | main = MLTT.main 7 | -------------------------------------------------------------------------------- /library/MLTT.hs: -------------------------------------------------------------------------------- 1 | module MLTT where 2 | 3 | import Control.Monad 4 | import MLTT.Infer 5 | import MLTT.Parser 6 | import MLTT.Types 7 | 8 | parseInfer :: (MonadThrow m) => Context -> String -> m Expr 9 | parseInfer ctx = parseExpr >=> infer ctx 10 | 11 | main :: IO () 12 | main = return () 13 | -------------------------------------------------------------------------------- /library/MLTT/Infer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | 3 | module MLTT.Infer where 4 | 5 | import Control.Monad 6 | import Control.Monad.Catch 7 | import Control.Monad.State.Strict (evalStateT) 8 | 9 | import Control.Lens hiding (Context) 10 | import Control.Lens.At 11 | 12 | import Data.Functor.Identity 13 | import Data.Maybe 14 | 15 | import qualified Data.HashMap.Strict as HM 16 | 17 | import MLTT.Types 18 | 19 | type Map k v = HM.HashMap k v 20 | 21 | infer :: (MonadThrow m) => Context -> Expr -> m Expr 22 | infer ctx = flip evalStateT 0 . inferType ctx 23 | 24 | refresh :: (MonadInfer m) => Variable -> m Variable 25 | refresh (NamedVar v) = do k <- get 26 | put $ k + 1 27 | return $ GenSym v k 28 | refresh (GenSym v _) = refresh (NamedVar v) 29 | refresh Dummy = refresh (NamedVar "_") 30 | 31 | subst :: (MonadInfer m) => Map Variable Expr -> Expr -> m Expr 32 | subst s (Var v) = return $ fromMaybe (Var v) $ s ^. at v 33 | subst _ (Universe k) = return $ Universe k 34 | subst s (Pi a) = Pi <$> substAbs s a 35 | subst s (Lambda a) = Lambda <$> substAbs s a 36 | subst s (App e1 e2) = App <$> subst s e1 <*> subst s e2 37 | 38 | substAbs :: (MonadInfer m) => Map Variable Expr -> Abstraction -> m Abstraction 39 | substAbs s (Abs v t e) = do v' <- refresh v 40 | t' <- subst s t 41 | e' <- subst (HM.insert v (Var v') s) e 42 | return $ Abs v' t' e' 43 | 44 | lookupType :: (MonadInfer m) => Variable -> Context -> m Expr 45 | lookupType v ctx = case ctx ^. at v 46 | of Just (t, _) -> return t 47 | Nothing -> throwSymbolNotFound v 48 | 49 | lookupValue :: (MonadInfer m) => Variable -> Context -> m Expr 50 | lookupValue v ctx = helper $ ctx ^. at v 51 | where 52 | helper (Just (_, Just e)) = return e 53 | helper (Just (_, Nothing)) = throwCouldNotEvaluate 54 | helper Nothing = throwSymbolNotFound v 55 | 56 | extend :: Variable -> Expr -> Maybe Expr -> Context -> Context 57 | extend v t x ctx = HM.insert v (t, x) ctx 58 | 59 | inferType :: (MonadInfer m) => Context -> Expr -> m Expr 60 | inferType ctx (Var x) = lookupType x ctx 61 | inferType _ (Universe k) = return $ Universe $ k + 1 62 | inferType ctx (Pi (Abs x t1 t2)) = inferTypeHelperPi ctx x t1 t2 63 | inferType ctx (Lambda (Abs x t e)) = inferTypeHelperLambda ctx x t e 64 | inferType ctx (App e1 e2) = inferTypeHelperApp ctx e1 e2 65 | 66 | inferTypeHelperPi :: (MonadInfer m) 67 | => Context -> Variable -> Expr -> Expr -> m Expr 68 | inferTypeHelperPi ctx v x y = do a <- inferUniverse ctx x 69 | b <- inferUniverse (extend v x Nothing ctx) y 70 | return $ Universe $ max a b 71 | 72 | inferTypeHelperLambda :: (MonadInfer m) 73 | => Context -> Variable -> Expr -> Expr -> m Expr 74 | inferTypeHelperLambda ctx v t e = do inferUniverse ctx t 75 | Pi . Abs v t 76 | <$> inferType (extend v t Nothing ctx) e 77 | 78 | inferTypeHelperApp :: (MonadInfer m) => Context -> Expr -> Expr -> m Expr 79 | inferTypeHelperApp ctx x y = do Abs v s t <- inferPi ctx x 80 | inferType ctx y >>= checkEqual ctx s 81 | subst (HM.singleton v y) t 82 | 83 | inferUniverse :: (MonadInfer m) => Context -> Expr -> m Word 84 | inferUniverse ctx t = do u <- inferType ctx t 85 | n <- normalize ctx u 86 | case n of 87 | Universe k -> return k 88 | _ -> throwTypeExpected 89 | 90 | inferPi :: (MonadInfer m) => Context -> Expr -> m Abstraction 91 | inferPi ctx e = do t <- inferType ctx e 92 | n <- normalize ctx t 93 | case n of 94 | Pi ab -> return ab 95 | _ -> throwFunctionExpected 96 | 97 | checkEqual :: (MonadInfer m) => Context -> Expr -> Expr -> m () 98 | checkEqual ctx x y = equal ctx x y >>= (`when` throwExpressionsNotEqual x y) 99 | 100 | normalize :: (MonadInfer m) => Context -> Expr -> m Expr 101 | normalize _ (Universe k) = return $ Universe k 102 | normalize ctx (Pi ab) = Pi <$> normalizeAbstraction ctx ab 103 | normalize ctx (Lambda ab) = Lambda <$> normalizeAbstraction ctx ab 104 | normalize ctx (Var v) = normalizeHelperVar v $ ctx ^. at v 105 | normalize ctx (App x y) = normalizeHelperApp ctx x y 106 | 107 | normalizeHelperVar :: (MonadInfer m) 108 | => Variable -> Maybe (Expr, Maybe Expr) -> m Expr 109 | normalizeHelperVar v Nothing = throwUnknownIdentifier v 110 | normalizeHelperVar v (Just (_, Nothing)) = return $ Var v 111 | normalizeHelperVar _ (Just (_, Just x)) = return x 112 | 113 | normalizeHelperApp :: (MonadInfer m) => Context -> Expr -> Expr -> m Expr 114 | normalizeHelperApp ctx x y = do 115 | x' <- normalize ctx x 116 | y' <- normalize ctx y 117 | case x' of 118 | Lambda (Abs v _ e') -> subst (HM.singleton v y) e' 119 | _ -> return $ App x' y' 120 | 121 | normalizeAbstraction :: (MonadInfer m) 122 | => Context -> Abstraction -> m Abstraction 123 | normalizeAbstraction ctx (Abs v t e) = do 124 | normalize ctx t 125 | Abs v t <$> normalize (extend v t Nothing ctx) e 126 | 127 | equal :: (MonadInfer m) => Context -> Expr -> Expr -> m Bool 128 | equal ctx x y = join (equal' <$> normalize ctx x <*> normalize ctx y) 129 | 130 | equal' :: (MonadInfer m) => Expr -> Expr -> m Bool 131 | equal' (Var v1) (Var v2) = return $ v1 == v2 132 | equal' (Universe k1) (Universe k2) = return $ k1 == k2 133 | equal' (App x1 y1) (App x2 y2) = (&&) <$> equal' x1 x2 <*> equal' y1 y2 134 | equal' (Pi a1) (Pi a2) = equalAbstraction a1 a2 135 | equal' (Lambda a1) (Lambda a2) = equalAbstraction a1 a2 136 | equal' _ _ = return False 137 | 138 | equalAbstraction :: (MonadInfer m) => Abstraction -> Abstraction -> m Bool 139 | equalAbstraction (Abs v1 t1 x) (Abs v2 t2 y) 140 | = (&&) <$> equal' t1 t2 <*> (equal' x =<< subst (HM.singleton v2 (Var v1)) y) 141 | -------------------------------------------------------------------------------- /library/MLTT/Parser.hs: -------------------------------------------------------------------------------- 1 | module MLTT.Parser where 2 | 3 | import Control.Monad.Catch 4 | import Control.Monad.IO.Class 5 | 6 | import qualified Data.HashSet as HS 7 | 8 | import Text.Parser.Char 9 | import Text.Parser.Combinators 10 | import Text.Parser.Expression 11 | import Text.Parser.LookAhead 12 | import Text.Parser.Token 13 | import Text.Parser.Token.Highlight 14 | 15 | import qualified Text.Trifecta.Delta as Trifecta 16 | import qualified Text.Trifecta.Parser as Trifecta 17 | import qualified Text.Trifecta.Result as Trifecta 18 | 19 | import MLTT.Types 20 | 21 | type Parser = Trifecta.Parser 22 | 23 | piSym, lambdaSym :: String 24 | -- piSym = "Π" 25 | -- lambdaSym = "λ" 26 | piSym = "pi" 27 | lambdaSym = "lambda" 28 | 29 | variableStyle :: IdentifierStyle Parser 30 | variableStyle = IdentifierStyle 31 | { _styleName = "variable" 32 | , _styleStart = letter 33 | , _styleLetter = alphaNum 34 | , _styleReserved = HS.fromList [piSym, lambdaSym] 35 | , _styleHighlight = Identifier 36 | , _styleReservedHighlight = ReservedIdentifier } 37 | 38 | variableP :: Parser Variable 39 | variableP = NamedVar <$> ident variableStyle 40 | 41 | referenceP :: Parser Expr 42 | referenceP = Var <$> variableP 43 | 44 | universeP :: Parser Expr 45 | universeP = Universe . fromInteger <$> (text "Set" >> natural) 46 | 47 | abstractionP :: String -> Parser Abstraction 48 | abstractionP binder = do symbol binder 49 | v <- variableP 50 | colon 51 | t <- exprP 52 | dot 53 | e <- exprP 54 | return $ Abs v t e 55 | 56 | piP :: Parser Expr 57 | piP = Pi <$> abstractionP piSym 58 | 59 | lambdaP :: Parser Expr 60 | lambdaP = Lambda <$> abstractionP lambdaSym 61 | 62 | appP :: Parser Expr 63 | appP = exprP >> someSpace >> exprP 64 | 65 | exprP :: Parser Expr 66 | exprP = choice [ universeP 67 | , piP 68 | , lambdaP 69 | , parens exprP 70 | , referenceP 71 | , appP ] 72 | 73 | testParseExpr :: (MonadIO m) => String -> m () 74 | testParseExpr = Trifecta.parseTest exprP 75 | 76 | parseExpr' :: (MonadThrow m) => Trifecta.Delta -> String -> m Expr 77 | parseExpr' delta str = case Trifecta.parseString exprP delta str 78 | of Trifecta.Success s -> return s 79 | Trifecta.Failure d -> throwParseException d 80 | 81 | parseExpr :: (MonadThrow m) => String -> m Expr 82 | parseExpr = parseExpr' mempty 83 | -------------------------------------------------------------------------------- /library/MLTT/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | 4 | module MLTT.Types ( module Exported 5 | , Context 6 | , MonadInfer 7 | ) where 8 | 9 | import MLTT.Types.Exceptions as Exported 10 | import MLTT.Types.Expr as Exported 11 | import MLTT.Types.Variable as Exported 12 | 13 | import Control.Monad.Catch as Exported (MonadThrow, throwM) 14 | import Control.Monad.State.Class as Exported (MonadState, get, put) 15 | 16 | import Data.HashMap.Strict (HashMap) 17 | 18 | type Context = HashMap Variable (Expr, Maybe Expr) 19 | 20 | type MonadInfer m = (MonadState Integer m, MonadThrow m) 21 | -------------------------------------------------------------------------------- /library/MLTT/Types/Exceptions.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | 3 | module MLTT.Types.Exceptions ( throwPretty 4 | , ParseException 5 | , throwParseException 6 | , InferException 7 | , throwCouldNotEvaluate 8 | , throwTypeExpected 9 | , throwFunctionExpected 10 | , throwSymbolNotFound 11 | , throwUnknownIdentifier 12 | , throwExpressionsNotEqual 13 | ) where 14 | 15 | import Data.Typeable 16 | 17 | import Control.Monad.Catch 18 | import Text.PrettyPrint.ANSI.Leijen 19 | 20 | import MLTT.Types.Expr 21 | import MLTT.Types.Variable 22 | 23 | -------------------------------------------------------------------------------- 24 | -- PrettyException ------------------------------------------------------------- 25 | -------------------------------------------------------------------------------- 26 | 27 | newtype PrettyException e = PrettyException e 28 | 29 | instance (Pretty e) => Show (PrettyException e) where 30 | showsPrec _ (PrettyException d) = displayS $ renderCompact $ pretty d 31 | 32 | instance (Typeable e, Pretty e) => Exception (PrettyException e) 33 | 34 | throwPretty :: (Typeable e, Pretty e, MonadThrow m) => e -> m a 35 | throwPretty = throwM . PrettyException 36 | 37 | -------------------------------------------------------------------------------- 38 | -- ParseException -------------------------------------------------------------- 39 | -------------------------------------------------------------------------------- 40 | 41 | newtype ParseException = ParseException Doc 42 | 43 | instance Pretty ParseException where 44 | pretty (ParseException d) = d 45 | 46 | throwParseException :: (MonadThrow m) => Doc -> m a 47 | throwParseException = throwPretty . ParseException 48 | 49 | -------------------------------------------------------------------------------- 50 | -- InferException -------------------------------------------------------------- 51 | -------------------------------------------------------------------------------- 52 | 53 | data InferException = IECouldNotEvaluate 54 | | IETypeExpected 55 | | IEFunctionExpected 56 | | IESymbolNotFound !Variable 57 | | IEUnknownIdentifier !Variable 58 | | IEExpressionsNotEqual !Expr !Expr 59 | 60 | instance Pretty InferException where 61 | pretty IECouldNotEvaluate = "Could not evaluate!" 62 | pretty IETypeExpected = "Type expected!" 63 | pretty IEFunctionExpected = "Function expected!" 64 | pretty (IESymbolNotFound v) = "Symbol not found: " <> pretty v 65 | pretty (IEUnknownIdentifier v) = "Unknown identifier: " <> pretty v 66 | pretty (IEExpressionsNotEqual e1 e2) = "Expressions not equal: " 67 | <> "'" <> pretty e1 <> "'" 68 | <> ", " 69 | <> "'" <> pretty e2 <> "'" 70 | 71 | throwCouldNotEvaluate :: (MonadThrow m) => m a 72 | throwTypeExpected :: (MonadThrow m) => m a 73 | throwFunctionExpected :: (MonadThrow m) => m a 74 | throwSymbolNotFound :: (MonadThrow m) => Variable -> m a 75 | throwUnknownIdentifier :: (MonadThrow m) => Variable -> m a 76 | throwExpressionsNotEqual :: (MonadThrow m) => Expr -> Expr -> m a 77 | throwCouldNotEvaluate = throwPretty IECouldNotEvaluate 78 | throwTypeExpected = throwPretty IETypeExpected 79 | throwFunctionExpected = throwPretty IEFunctionExpected 80 | throwSymbolNotFound v = throwPretty $ IESymbolNotFound v 81 | throwUnknownIdentifier v = throwPretty $ IEUnknownIdentifier v 82 | throwExpressionsNotEqual e1 e2 = throwPretty $ IEExpressionsNotEqual e1 e2 83 | -------------------------------------------------------------------------------- /library/MLTT/Types/Expr.hs: -------------------------------------------------------------------------------- 1 | module MLTT.Types.Expr where 2 | 3 | import Text.PrettyPrint.ANSI.Leijen 4 | 5 | import MLTT.Types.Variable 6 | 7 | data Abstraction = Abs Variable Expr Expr 8 | deriving (Show, Eq, Ord) 9 | 10 | instance Pretty Abstraction where 11 | pretty (Abs v t e) = pretty v <> " : " <> pretty t <> " . " <> pretty e 12 | 13 | data Expr = Var !Variable 14 | | Universe !Word 15 | | Pi !Abstraction 16 | | Lambda !Abstraction 17 | | App !Expr !Expr 18 | deriving (Show, Eq, Ord) 19 | 20 | instance Pretty Expr where 21 | pretty (Var v) = pretty v 22 | pretty (Universe n) = string $ show n 23 | pretty (Pi a) = "Π " <> pretty a 24 | pretty (Lambda a) = "λ " <> pretty a 25 | pretty (App e1 e2) = pretty e1 <> " " <> pretty e2 26 | -------------------------------------------------------------------------------- /library/MLTT/Types/Variable.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveDataTypeable #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | 4 | module MLTT.Types.Variable where 5 | 6 | import Data.Data (Data) 7 | import GHC.Generics (Generic) 8 | 9 | import Data.Text (Text, unpack) 10 | 11 | import Data.Hashable 12 | import Text.PrettyPrint.ANSI.Leijen 13 | 14 | data Variable = NamedVar !Text 15 | | GenSym !Text !Integer 16 | | Dummy 17 | deriving (Show, Eq, Ord, Generic, Data) 18 | 19 | instance Hashable Variable 20 | 21 | instance Pretty Variable where 22 | pretty (NamedVar str) = pretty $ unpack str 23 | pretty (GenSym str _) = pretty $ unpack str 24 | pretty Dummy = "dummy" 25 | -------------------------------------------------------------------------------- /mltt.cabal: -------------------------------------------------------------------------------- 1 | name: mltt 2 | author: Sebastian Conybeare 3 | version: 0.0.1 4 | build-type: Simple 5 | cabal-version: >= 1.10 6 | category: Utility 7 | copyright: 2016 Sebastian Conybeare 8 | extra-source-files: README.md 9 | maintainer: sebmathguy@gmail.com 10 | -- FIXME: add license file etc. 11 | synopsis: An implementation of Martin-Löf Type Theory in Haskell. 12 | description: An implementation of Martin-Löf Type Theory in Haskell. 13 | 14 | library 15 | build-depends: base == 4.* 16 | 17 | -- Data types 18 | , containers == 0.5.* 19 | , unordered-containers == 0.2.* 20 | 21 | -- Strings 22 | , text == 1.2.* 23 | 24 | -- Monad transformers 25 | , mtl == 2.2.* 26 | , transformers == 0.4.* 27 | 28 | -- Lens 29 | , lens == 4.13.* 30 | 31 | -- Pipes 32 | , pipes == 4.1.* 33 | 34 | -- Parsing 35 | , trifecta == 1.5.* 36 | , parsers == 0.12.* 37 | 38 | -- Exceptions 39 | , exceptions == 0.8.* 40 | 41 | -- Miscellaneous 42 | , haskeline == 0.7.* 43 | , ansi-wl-pprint == 0.6.* 44 | , data-fix == 0.0.* 45 | , here == 1.2.* 46 | , hashable == 1.2.* 47 | 48 | default-language: Haskell2010 49 | exposed-modules: MLTT 50 | MLTT.Types 51 | MLTT.Types.Expr 52 | MLTT.Types.Variable 53 | MLTT.Types.Exceptions 54 | MLTT.Infer 55 | MLTT.Parser 56 | ghc-options: -Wall 57 | -threaded 58 | -fno-warn-type-defaults 59 | -fno-warn-unused-imports 60 | -fno-warn-unused-do-bind 61 | ghc-prof-options: -auto-all -prof 62 | hs-source-dirs: library 63 | default-extensions: OverloadedStrings 64 | 65 | executable mltt 66 | build-depends: base == 4.* 67 | , mltt 68 | default-language: Haskell2010 69 | ghc-options: -threaded 70 | ghc-prof-options: -auto-all -prof 71 | hs-source-dirs: executable 72 | main-is: Main.hs 73 | --------------------------------------------------------------------------------