1. Problem and Motivation111Extended abstract submitted to ICFP 2018 SRC. Research Advisor: J. Garrett Morris, firstname.lastname@example.org
Managing resources—file handles, database connections, etc.—is a hard problem. Debugging resource leaks and runtime errors due to resource mis-management are difficult in evolving production code. Programming languages with static type systems are great tools to ensure erroneous code is detected at compile time. However, modern static type systems do little in the aspect of resource management as resources are treated as normal values. We propose a type system, , based on the logic of bunched implications ()(ohearn_logic_1999) which models resources as first class citizens. We distinguish two kinds of program objects—restricted and unrestricted—and two kinds of functions—sharing and separating. Our approach guarantees resource correctness without compromising existing functional abstractions.
For a concrete example, we consider the case of file handling. In Haskell, a file being closed twice or a file not being closed at all may cause run-time errors but it not flagged as a type error. We represent separating functions, i.e. functions that do not share resources with their arguments using , and sharing functions i.e. functions that share resources with their arguments using . In , the type signatures of the file handling API explicitly states that they are separating in nature. This accounts for closing the file handle more than once. Each program object needs to be explicitly dropped if it has to be treated as a resource, as in linear type systems (ahmed_l3_2007; mazurak_lightweight_2010; bernardy_linear_2017). This accounts for failing to close the file handles.
Exception handling in Haskell can be done using MonadError(liang_monad_1995). However, it does not give a systematic way of cleaning up resources in case of run-time exceptions. We consider the case where a critical section of the code throws an exception as shown in Fig. 1. The IOF describes the fact that the computation can throw exceptions, while IO does not. The catch function has a sharing argument, hence it can access the file handle fh declared in the part of the code that can throw exceptions and close it before exiting to prevent a memory leak.
|openFile :: FilePath -* IO FileHandle closeFile :: FileHandle -* IO () readFile :: FileHandle -* IOF (String, FileHandle) writeFile :: String -* FileHandle -* IOF ((), FileHandle) throw :: Exception -* IO a catch :: IOF a -* (Exception -* IO a) -¿¿ IO a||readFromFile :: FilePath -* IO (Either String String) readFromFile fpath = do fh ¡- openFile fpath ((s, fh) ¡- readLine fh let l = caps s closeFile fh return Right l) ‘catch‘ (-¿ do closeFile fh return Left ”read file error”)|
2. Background and Related Work
Type systems based on linear logic(girard_linear_1987; wadler_taste_1993; ahmed_l3_2007; mazurak_lightweight_2010; bernardy_linear_2017) provide one technique to solve the resource control problem. They restrict the structural rules of weakening and contraction to view all values as resources. This changes the meaning of the connectives as well. Linear implication means “A is consumed to obtain B”. We also get additive and multiplicative fragments of conjunction ( means “both A and B” and means “choose between A and B”). There is, however, an awkward asymmetry in this system—while is the right adjoint of , has no such counterpart. Logic of (pym_semantics_2002) repairs this asymmetry between implication and conjunction. It uses trees as contexts, where the internal nodes are either comma () or semicolon () and leaf nodes are the propositions. The structural rules—weakening and contraction—are prohibited for propositions connected using (). but . The multiplicative conjunction gets a multiplicative implication and the additive conjunction gets the additive implication as its right adjoint. The Curry-Howard interpretation of is in terms of sharing in rather than linear logic’s consumption. If the function does not share resources with its argument is used, while if the function shares resources with its arguments, is used instead.
Jones(jones_theory_1994; jones_qualified_2003) introduces qualified types, a general framework to incorporate predicates for polymorphism. The Hindley-Milner type system(milner_theory_1978) extended with qualified types(jones_simplifying_1995) can express type classes with functional dependencies(mark_type_2000), and first class polymorphism(jones_first-class_1997). Morris(morris_best_2016) uses qualified types to design Quill, a functional language with linear calculus. In Quill, the predicate specifies the type is unrestricted i.e. it can be duplicated or dropped at will, or it does not contain any resources. Proof theoretically, the type is tagged unrestricted whenever weakening and contraction is admissible. A binary predicate helps generalize function definition in presence of restricted types. specifies that type admits more structural rules than type .
3. Approach and Uniqueness
is an extension of standard call-by-name lambda calculus based on logic of . We introduce two kinds of lambdas associated with the two implications. introduces a separating function , while introduces a sharing arrow . We generalize the use of trees as contexts in to graphs of sharing information. We represent sharing graphs as adjacency lists in the environment context. A triple would mean of type is in sharing with . The sharing relation is a symmetric, reflexive and non-transitive. We say that the contexts are in complete sharing——if all the variables are shared and they are disjoint——if they are not shared. We formally define them in Fig. 2, where means disjoint. The predicates and range over sharing and separating functions respectively. We include predicates and as is from Quill. The complete type system is shown in Fig. 3.
4. Results and Contributions
is a novel sub-structural -calculus that generalizes Curry-Howard Interpretation of . We have developed a sound and complete syntax directed type system and designed a type inference algorithm based on Algorithm (lee_proofs_1998). We have extended our system to support kinds with user defined type constructors allowing programmers to define data types with sharing and separating fields. The use of monads with sharing and separating functions can statically detect resource errors, while expressing patterns like exceptions and non-determinism that are difficult to capture in linear languages as described in previous section.