Ask HN: Any logic based software dev tool which helps building code gradually?
My main idea is that as a programmer what I do is constantly reducing the set of all programs to a particular program which satisfies the specification. (At least that is what I think I should do to get a flexible codebase where adding/removing quasi orthogonal constraints is easy: that is where declarative/logic programming, Prolog, rules / inferring assistants come to my mind.)
When thinking, reasoning about timing, concurrency, async, correctness happens 1. Step by step, gradually 2. using nothing but logic.
We have some tools: eg. type systems to express interdependence (constraints) and they are also useful in that "step by step" gradual manner: it warns me if I misstep, but there is much more information I could get from a "dev assistant".
When I am thinking I (have to) enumerate all the possible cases for a given scenario, but I can't really reuse the existing logic statements I have (or ever had!) in my mind, because usually the code is not written in logic and neither the environment processes it to give me insights.
Eg. I would like to make logic statements on the ordering of method calls (or basically just events) to make sure it won't conflict with another ordering (there must be at least one ordering which satisfies for all).
I would like to toggle on and off my logic constraints to have the currently relevant ones only (to help me to develop other parts of the system), save the sets as "views" and combine them to see if there is any conflict between their related logic statements (and if so I could see what and I could fix it).
I'd need higher order logic (eg. existential operator) to make statements about my "desired" program: to help/assist my future self not to forget about that "constraint".
Are you aware of such an IDE/tool/project?
Agda, Coq, etc might do something similar I just did not realize.
I would really appreciate your help, I'm totally obsessed with that.
59 comments
[ 2.9 ms ] story [ 80.4 ms ] threadBut I will give a shot if there is nothing else.
What are the main differences (pros/cons) using TLA+ vs Dependent Types (and proving the algorithm is fine) for system verification?
The complexity of Asynchronicity & Concurrency lead me to the formal expression of time related logic: Temporal Logic (https://en.wikipedia.org/wiki/Temporal_logic) There are multiple temporal logics with different level of usefulness. (Check Temporal logics section.) I really want to solve harder problems and express deeper relations, so it seems to me what I’d need is CTL at least (https://en.wikipedia.org/wiki/Computation_tree_logic).
I thought I might want to model my software (any kind of app!) and prove it is working as intended using CTL (or other more expressive TL) in Agda/Idris (Coq/TLA+) and then make a similar implementation in another language.
Eg. there is an LTL implementation in Agda, but I did not find similar things for CTL.(https://github.com/xekoukou/LTL)
What do you think, is it a good idea to express my model using (C)TL in Agda/Idris, or I should use a more dedicated piece of technology for that problem? Is there a limit in case of Agda/Idris for that in theory?
Any resource is appreciated!
Agda and Coq are pure functional languages, and their type systems are a consistent logical system. They are more mathematically oriented and may not be what you want.
Agda Emacs: https://agda.readthedocs.io/en/v2.6.0.1/tools/emacs-mode.htm...
Coq Tactics: https://coq.inria.fr/refman/proof-engine/tactics.html
Off: may I ask how did you end up with F# and how old are you? I can't really decide the average age of programmers who do know dependent types.
In type theory, there are things called "terms" and "types." A term is something like `fun x -> x`, or `(1, 2)`, or `1 + 1`. In short, a term is basically an expression. Each term has a type. For example, `fun x -> x` might have the type `a -> a` and `(1, 2)` might have type `Nat * Nat`. If a term has a type, it "inhabits" the type. "x has type T" is written `x : T`.
According to an idea called the Curry-Howard correspondence, types can encode logical statements such that terms that inhabit the type are its proofs. Therefore, by inhabiting a type, you are really proving a theorem.
With dependent types, the term and type languages are the same language, so you can mix them together. In short, types are terms.
Here are some types in Martin-Lof type theory:
The unit type is a type with one inhabitant, (). It can be interpreted as the type of the empty tuple. In the Curry-Howard correspondence, the unit type corresponds with truth. A * B, the product type of A and B, is the type of pairs. It is based on the Cartesian product operation: https://en.wikipedia.org/wiki/Cartesian_product In the Curry-Howard correspondence, the product type corresponds with logical conjunction (and). By proving A and proving B, you can prove A * B (AKA A /\ B). The empty type is the type with no inhabitants. If you have an inhabitant of the empty type, you can eliminate it to derive anything. The empty type corresponds with falsity, and its elimination means that false implies anything. The sum type is the tagged union, meaning "this or that." The type A + B means that the Inl tag holds an A and the Inr tag holds a B. The sum type corresponds with logical disjunction (or). The exponential type, the type of functions, means logical implication. If you have f, a proof that A implies B, and x, a proof of A, you can prove B by doing f(x).With dependent types, the product and exponential types are generalized into the dependent sum (sigma) and the dependent product (pi) types respectively.
With the dependent sum type, or sigma type `Σ (x : A) B(x)`, the second component in the type is actually a function of A (meaning that B : A -> Type). The dependent sum type generalizes the product type so that the type of the second element of the pair is actually dependent on the first element. In the Curry-Howard correspondence, the sigma type corresponds with existential quantification, "there exists" or "for some." To prove Σ (x : A) B(x), you must inhabit A (show that A exists) and B(x) (where B is a logical statement about x, the inhabitant of A).
With the dependent product type, or pi type `Π (x : A) B(x)`, B is also a function of A that returns a type. The dependent product type generalizes the exponential type so that the codomain of the function, B(x), is dependent on the function's input, x. The dependent product type corresponds with universal quantification, "for all." To prove Π (x : A) B(x), you must prove B(x) for every possible x, where B(x) is a logical statement about x.
Why the dependent product type generalizes the exponential type and the dependent sum type generalizes the product type seems confusing at first glance, but it really isn't:
Σ for x = 1 to n, B(x) = B(1) + B(2) + ... B(n), a...
How did study those things? Did you have any support? I have multiple programmers in my family but no one is aware of things, lol.
I learn type theory from reading on the Internet. My father is a programmer, but he mainly uses C++, so he wouldn't know type theory.
To be honest, I probably wouldn't have learned about type theory if it weren't for the Advanced Topics subculture. My first exposure to programming was with Scratch, and on the Scratch "Advanced Topics" forum (ATs), there is a subculture of people (ATers) who are more knowledgeable about programming than the average user and enjoy pushing Scratch to its limits. (I guess you could say that the ATers are hackers.) One of my friends from the ATs is interested in programming language stuff, and many people there are Lisp hackers. So, the poeple in the ATs are probably responsible for stimulating my curiosity and directing me towards functional programming and PL theory. However, I wouldn't say that I learned type theory from the ATs.
Oh yeah, I also learn type theory from people on the Fediverse (the federated social network that Mastodon uses), as there are people there who are knowleadgable about type theory.
Is there any slack channel/group you are part of? What did you find?
F# simply because I was looking for a functional-programming job and this was one of the first that came up. (Great personal fit, using technologies I'm not ill-disposed towards, good pay etc.)
https://wiki.haskell.org/GHC/Typed_holes
Even OCaml can kind of simulate type holes:
https://www.reddit.com/r/ocaml/comments/2ulom8/a_trick_for_s...
Purescript has nice IDE integration and type holes (Haskell likely has something similar, not sure): https://qiita.com/kimagure/items/f1827c9129f3ee6ede35#type-h...
The Purescript integration seems definitely interesting!
Do you use these tools for work?
To be honest, Haskell and OCaml (which I have used at work) have similar language servers available for IDE integration, but I'm a vim user and tend to use plain Vim along with an open REPL (making use of type holes in the REPL). But I've been meaning to explore the IDE integrations of Haskell and OCaml as well as Purescript.
It's pretty awesome.
Am I missing something?
For your specific example it seems easiest to just go with a pure functional language. Haskell compilers do reorder statements in fairly radical ways.
I like the approach of starting from the set of all programs, and gradually refining the set to specific desires. There has been a minor tradition of research here, for example Dijkstra's Guarded Command Language and the notion of Stepwise Refinement. I tangentially used this imagery in http://akkartik.name/post/modularity.
I don't know much about proof assistants but I did not see how someone can force the assistant to focus only on particular parts and save these parts for later time.
There might be multiple holes in the implementation and I would like to study them sometimes together but sometimes separately.
In any case, I think you'll enjoy hanging out at https://futureofcoding.org/slack-readme where people often chat about different parts of this (huge) open problem. I do.
That group seems really cool! Thanks!
(Like many others in that group, I'm trying to start from scratch: https://github.com/akkartik/mu/blob/master/subx/Readme.md. My focus isn't directly on the tools you describe. But hopefully it'll one day be closer to the main trunk of evolution rather than the dead end the current mainstream seems to be. And it will support a more flexible software stack that can adopt such tools.)
Edit: I should specify: within the constraints of today's language technology, it's hard for me to imagine a language-independent implementation. It's entirely possible things will be different 100 years from now, although for various reasons I'm skeptical.
I did not mean the tool should exist independently, I meant some kind of extra IDE "layer" beside a "regular"/widespread (C,Java,C#) compiler/language. But ofc there is no such thing like "regular" compiler/language
An alternate point of view is to start with the specification, interpreted as a generally nondeterministic program, and then refine it using logically sound optimizations to render it executable. This is roughly the approach of Fiat: http://adam.chlipala.net/papers/FiatSNAPL17/
They’ve used it to generate crypto primitives that are in use: http://adam.chlipala.net/papers/FiatCryptoSP19/
Can you please explain 1. the potential gains you have with the packages above 2. the drawbacks of using these opposed to a Dependent Typed language?
It would help a lot to me.
There is much information you can specify than later extract from the already given piece of code eg. with dependent types.
Microsoft Research's project Everest is using it to write a verified HTTPS stack.
Edit: formatting
Is it a good idea?
http://www.event-b.org
https://en.wikipedia.org/wiki/Rodin_tool
Here the inventor demonstrates making a simple bank account simulator with the stepwise refinement approach:
https://www.youtube.com/watch?v=fSWZWXx5ixc