I am a programmer and a programming language researcher.
As a programmer, I love programs that solve complex problems in a simple way. I write programs in a lot of styles, imperative, object-oriented, functional, etc. I always try to refactor my code to be easily understandable by others and my future self.
My research revolves around the design and implementation of programming languages to make programs more reliable, secure, faster, resource-efficient, easier to construct and maintain.
Welcome to contact me at fengyun.liu.cs@gmail.com.
Topics of Interest
Language design, domain-specific languages, program analysis, compiler construction, type theory, concurrency, program logics, meta-programming, non-classical logics, reasoning about uncertainty.
When Program Analysis Meets Type System
I’m particularly interested in combining the strength of type system and program analysis to create explainable, fast, modular and sound static analyses: to design better programming languages that prevent more errors of programs and enable more compiler optimizations without incurring overhead on programmers.
Type systems are modular, the rules are simple and clear, and the check is fast. However, for non-trivial properties of programs, an expressive type system will be verbose and curbersome to use. That is the reason why we haven’t seen wide adoption of more advanced type systems despite the success of relatively simple type systems in the industry.
On the other hand, static program analysis incurs zero or low overhead, but they are usually not modular, the rules are complex and prone to changes, and the performance concern hinders their integration in the compiler. The high turnaround cost of false positives from their usage in CI and the obscurity and unstability of the rules make them much less popular despite their usefulness in constructing better programs [1, 2].
I firmly believe that the combination of type system and program analysis will help create better programming languages that check more advanced properties of programs. The synergy of the two requires ingenuity in designing abstractions and algorithms to achieve simplicity, speed, modularity, soundness and expressiveness. The work on safe initialization in Scala is our first endeavor in this direction.
[1] Sadowski, Caitlin, Edward Aftandilian, Alex Eagle, Liam Miller-Cushon, and Ciera Jaspan. 2018. Lessons from Building Static Analysis Tools at Google. Communications of the ACM 61 (4): 58–66.
[2] Distefano, Dino, Manuel Fähndrich, Francesco Logozzo, and Peter W. O’Hearn. 2019. Scaling Static Analyses at Facebook. Communications of the ACM 62 (8): 62–70.
There are No Domains in Domain-Specific Languages
It is widely known that domain-specific languages may improve reliability, security, maintainability and performance of programs as well as boost productivity of programmers. However, it is less known that there are no domains in domain-specific languages and it is often forgotten that domain-specific languages are languages.
Designing domain-specific programming languages is hard, as pointed out by Tony Hoare [3] : (1) The design of a DSL needs to address all problems in general-purpose language design, such as abstractions for code reuse, type checking, effects and exceptions, polymorphism, separate compilation, etc. (2) Users will ask for more features to be supported in the DSL and eventually make it as complex as general-purpose languages.
The design of a good domain-specific language is challenging and it requires well-informed tradeoffs between expressiveness, safety, simplicity and performance. I believe one methodological approach to tackle the challenge is to center the design around domain-inspired abstractions and paradigms. Our work on implicit state machines is an incarnation of this methodology.
[3] Hoare, C.A.R., 1978. Everything You’ve Wanted to Know about Programming Languages but Have Been Afraid to Ask.
Why Language Matters
Mathematical language is widely recognized as the best tool to create, represent, and share knowledge and logical-mathematical artifacts. Formal languages enable more accurate representation of knowledge and more efficient creation of artifacts for wider domains that even machines can “understand”. The research in formal languages provides apparatus to support the creation, representation, and study of complex systems involving structure, time, space, order, inference, resources, capabilities, constraints, relations, algorithms, processes, rules, protocols, dependencies, etc.
We witness a growing demand for programming languages:
First, the plethora of new computing mediums and paradigms calls for new programming methods. To name just a few — distributed computing, non-volatile memory, FPGAs, GPUs, domain-specific architectures/accelerators, near-memory computing, analog computing, optical computing and quantum computing.
Second, there is a strong call for better programming paradigms from various domains. For example, mechanized mathematics, system software, smart contracts, real-time and critical software, software-defined network, software-defined radio, signal processing, hardware design, artificial intelligence, geometrical modeling, scientific simulation, robotics, metamaterials, etc. These domains vary in their requirement for expressiveness, security, reliability, debuggability, performance and ease of programming. Programming languages based on a careful combination of pearls in programming language research can better cater to the requirement of a particular domain.
Not all languages are equal:
An unreliable programming language generating unreliable programs constitutes a far greater risk to our environment and to our society than unsafe cars, toxic pesticides, or accidents at nuclear power stations. Be vigilant to reduce that risk, not to increase it.
The tools we use have a profound (and devious!) influence on our thinking habits, and, therefore, on our thinking abilities. … The use of COBOL cripples the mind; its teaching should, therefore, be regarded as a criminal offence.
The Road to High Quality Software
Here is my opinion:
- Carefully define system boundary and interface
- Invent good abstractions for the domain
- Create simple, reliable and checkable contracts between modules/sub-systems
- Establish, document and check invariants about data at all levels
- Facilitate implementation with reasonable product design constraints
- Reduce shared mutable state and localize synchronization for concurrency safety
- Make distributed systems testable by virtualizing peers, network errors and delays
- Use a statically typed and memory-safe language
- Extensive tests that are reusable across big refactoring
- Quantify and track major metrics of the system
- Increase observability of long-running systems
- Minimize the depth of external library dependencies