Hi Shimin and welcome! For me ATS is a place where I can bring some dry linear logic calisthenics to life with programming. So let's get started.
On Saturday, June 1, 2019 at 2:33:37 AM UTC+3, Shimin Guo wrote: > > I'm thinking of using linear types for system administration. > Specifically, I want to add type annotations to commands like mkdir, rm, > etc., so I can reason about the state of the system after they are executed. > > For example, mkdir will have the type > > p: path -> isdir(p) > > where isdir(x) is a linear prop. rmdir will consume that prop. > > What I'm currently stuck on is the following scenario: > > touch a/b/c > rm -r a > > After the touch, there should be a linear prop exists(a/b/c), and after > the rm, the linear prop needs to be consumed. My question is, what should > the type signature for "rm -r <dir>" be? It seems it needs to consume all > the linear props in the "environment" in the form of exists(p) where p is > under <dir>. Is there a way to represent such a signature in a language > like ATS? > I think it would make sense to somehow "leave" b and c to "be", i.e. discard the handles to them but leave them as-is in the filesystem. That could be a no-op function, even, but of course, for total correctness you don't want any other process to have links to b or c... So as you can see, it gets a bit problematic. However, putting this scenario aside, you could have a function to "free" the handles, then use it to dispose of the handles, and then finally remove a. I once wrote a variadic macro that helped me to clear out such linear-typed variables in one line. Here are a few rambling thoughts about your problem. Please take with a grain of salt. It's really complicated to do this as I describe below (with the indexes), but might give some ideas. We could represent the filesystem as labelled tree (every label is the file or directory name). Directories may contain subdirectories and files (files cannot be nested). So it can be seen as a graph, where files/directories are two sorts of nodes, and containment relation between any two nodes are the edges. Then a path is just a sequence of steps through the tree nodes, same as in graph theory. For example, a is a node of sort "directory", b is a node of sort "directory", b is contained in a, c is a node of sort "file", c is contained in b, and also, the file path a/b/c is a sequence of steps, starting at a, following through the "contained in" relationship between nodes, and ending at "c". Since it makes sense to think of these nodes as resources, then we represent these with linear types. Then we are left with the containment relation. Imagine we have an abstract linear type fsnode(bool) where fsnode(true) is assigned to files and fsnode(false) is assigned to directories. We could also introduce some casts if we wanted to (e.g. introduce a type "file" that is convertible to fsnode(true) and vice versa, and a type "directory" that is convertible to fsnode(false) and vice versa). One way to deal with it is to index every node with an integer denoting the count of subdirectories/files that it contains. Then you'd have an fsnode(bool,int), where the second index is the count of subdirectories/files that are contained in the directory (for files it would be always 0, of course). This kind of solution is not very practical, though. So another approach could be to track in the indexes two more bits of information about the node: 1. the identity of a node (as something opaque, e.g. an addr index) 2. the identity of a node's parent Then for a root directory node you'd have a type like fsnode(false, p, null) where p is some non-null address, And for a subdirectory that's under the root directory you'd have have a type like fsnode(false, p1, p), that is, it's contained in p, and p1 is not the same as p (this would be usually discharged by the typechecker, we don't really care about it). So in your case, it would go like this, in code: val r = fs_root () // : fsnode(false, r, null) val a = mkdir (r, "a") // : fsnode(false, a, r) val b = mkdir (a, "b") // : fsnode(false, b, a) val c = mkdir (b, "c") // : fsnode(false, c, b) val () = free (c) // [c] is no more in our code val () = free (b) // [b] is no more in our code val () = rm (a, true(*recursive*)) // [a] is removed, recursively val () = free (r) // we don't need [r] anymore The "free" and "fs_root" are more like "leasing" the resources from OS (i.e. asking for them to use temporarily, but then eventually we are obliged to give them back to their justful owner). Since these functions that we give to the user are the only way to legitimate work with the abstract types, they will have to comply with the protocol that we impose on them, or their program will fail to compile. A similar approach is taken below (please see the provided tests, too, since they are actual programs you can run): https://github.com/ashalkhakov/typesafe-dom/blob/master/src/SATS/dom.sats It depends on what you want to accomplish. This approach is very fine-grained and may be too difficult to work with. -- You received this message because you are subscribed to the Google Groups "ats-lang-users" group. To unsubscribe from this group and stop receiving emails from it, send an email to ats-lang-users+unsubscr...@googlegroups.com. To post to this group, send email to ats-lang-users@googlegroups.com. Visit this group at https://groups.google.com/group/ats-lang-users. To view this discussion on the web visit https://groups.google.com/d/msgid/ats-lang-users/5f9a7255-20ef-4aad-9002-2e37680e6e8e%40googlegroups.com.