When we think about reasoning in programming, the first thing that comes to mind is logic programming and a rule-based approach, expert systems and business rule management systems (BRMS).
The common multi-paradigm languages practically do not include these approaches, although they work with them through libraries and interfaces. Why? Because these languages can not include themselves forms, which in some sense contradict their essence. Popular programming languages usually work with determinism (expected data, usage scenarios, etc.), while approaches that use reasoning usually work with uncertainty (unpredictable data, usage scenarios, etc.). The reasoning will be different in both cases too. In the first, argues the architect or developer, in the second, the reasoning / rule engine argues.
We cannot prefer this or that side of this dualism. The external world is full of determinism and uncertainty, they are combined in the same things and phenomena. For example, every day we can go home on the same shortest route. We have already spent time in the past on its search, we know its features (so that we can go over it, almost "on the machine"). Using this route is very efficient, but not flexible. If traffic jams and other circumstances, the shortest route may be the longest. We can use the navigator, but it does not give a full guarantee. A traffic jam can form when we are on the road and there are simply no turns to make a different route. We ourselves can know that in 30 minutes there will be a daily traffic jam just on the route suggested by the navigator. The conclusion is that it is better to have a choice between the decision that was once chosen (and not spend time on it again) and flexible ways (if circumstances have changed).
Can we combine these approaches in code? The most popular solutions for now are: plug-ins, customizations, domain-specific languages (DSL), rules already mentioned, etc. Plugins are limited to the need to write code and require a certain level of competence. Customizations, rules, and domain-specific languages are limited by the need to learn and the functionality of the application that is available to them. Can we facilitate the study and give access to the greatest possible functionality of the application? One possible solution: meaning markup language . What can he do?
Consider this on the example of calculating the volume of the planets. A classic solution in an object-oriented language might look like this:
class Ball { int radius; double getVolume() { ... } } class Planet extends Ball { ... } Planet jupiter = new Planet("Jupiter"); double vol = jupiter.getVolume();
In this code, reasoning is sufficient: in definitions, fields, methods, class hierarchies, etc. For example, the fact that the planet is a ball, and Jupiter is a planet. However, these arguments are implicit, cannot be reused, are highly interdependent (tightly coupled) with the code. In similar code in JavaScript, the reasoning is hidden in conventions:
function getBallVolume() { ... } var jupiter = planet.getPlanet('Jupiter'); var volume = getBallVolume(jupiter.diameter);
When we use meaning markup:
meaningful.register({ func: planet.getPlanet, question: ' {_} {} {} ', input: [{ name: '', func: function(planetName) { return planetName ? planetName.toLowerCase() : undefined; } }], output: function(result) { return result.diameter; } }); meaningful.register({ func: getBallVolume, question: ' {_} {} {} ', input: [{ name: '' }] }); meaningful.build([ ' { } ', ' {} ' ]); expect(meaningful.query(' {_} {} {} ')).toEqual([ 1530597322872155.8 ]);
But the situation is different because:
And it's all? Not really. Now we can enter into areas that are not in the main programming languages, for example, cause and effect. Imagine that we are dealing with instructions on how to install the operating system OS_like_OS. Modern software considers such instructions as textual documentation. But we can assume that this is a set of causes of the OS installation. And in this case we can get an answer to the question "How to install OS_like_OS?" directly:
var text = [ ' ', ' USB ', ' ', ' ' ]; _.each(text, function(t) { meaningful.build(' { } OS_like_OS { } ' + t); }); var result = meaningful.query('{_ @} { } OS_like_OS'); expect(result).toEqual(text);
Very easy to reason? But this is only the beginning. After all, when answering such questions, we can operate not only with cause and effect, but with conditions and other relationships. You can see an example of a test that queries the path to functionality with alternatives and conditions. This is more than documentation. These are explanations and a dependency map between the components of the application, which can be reused, including to answer the many questions "How?" and why?".
A similar situation with error handling. Today, errors are, at best, readable messages with a stack of calls that can tell what happened. In the worst case, it may be a runtime error message without any prompts. What the markup of meaning and its integration with the code can improve is to help better clarify what actually happened, and, perhaps, help to correct the situation. Let's look at the example of a function that adds items to the list:
// , / . var addEnabled = true; var planets = []; meaningful.register({ func: function(list, element) { if (addEnabled) eval(list + '.push(\'' + element + '\')'); else // false, throw " "; }, question: ' {} {} {} ', input: [ { name: 'list' }, { name: '' } ], error: function(err) { if (err === ' ') // return ' {} '; } }); // , meaningful.register({ func: function(list, element) { addEnabled = true; }, question: ' {} ' }); meaningful.build([ 'planets { } ' ]); meaningful.build([ ' { } ' ]); meaningful.build([ ' { } ' ]); meaningful.query(' {} {} {} planets', { execute: true }); // , .. true expect(planets).toEqual([ '' ]); // . addEnabled = false; // meaningful.query(' {} {} {} planets', { execute: true, error: function(err) { // "" , meaningful.query(err, { execute: true }); }}); expect(planets).toEqual([ '' ]); meaningful.query(' {} {} {} planets', { execute: true }); // expect(planets).toEqual([ '', '' ]);
As we can see, the result of error handling can be not only a message and a call stack, but also a complex of cause and effect, variable states, etc., all that can help answer the question "Why did this error happen?". To make this possible, the preparation must begin at the stage of formulating the requirement, when we can construct layouts of the facts based on the textual description of the problem. For example, this markup:
{} { } 142984 4.1887902047864 { } {} {} { } 2 1530597322872155.8 { } {} {} { } 142984
Can be considered as the layout and expectations of the functions getPlanetDiameter () and getBallVolume (). Based on this, true layout functions can be generated:
function getDiameterOfPlanet(planet) { if (planet === '') return 142984; } function getVolumeOfBall(diameter) { if (diameter === 2) return 4.1887902047864; if (diameter === 142984) return 1530597322872155.8; }
Such layout functions already allow reasoning (for example, to help calculate the volume of Jupiter), which can help assess how the future application can fit into an already existing data and code ecosystem. Further, the developer can already replace the layout function with real code. That is, thanks to the correspondence between requirements, code, tests, user interface, documentation (which is supported by compositing natural language constructs), we can work with a limited layout function, a real function, a corresponding part of the user interface and documentation in a similar way. This allows us to further shorten the waiting cycle between various types of engineering activity, as is the case with reasoning: in order to work with them, we do not need to wait for full implementation.
Naturally, the performance issue is critical for the execution phase, but, unlike the Semantic Web, which seeks to build the Giant Global Data Graph , your application can only be limited to its own data. That will not lead to more flexible and global conclusions (since we are limited only by our application), but will also be more specific and verifiable (since we are limited only by our application).
So can your code reason? More precisely, rather, it will be done not by the code itself, but by the output machine based on your code, but it is feasible. If only because we can be motivated by improved application development, due to the fact that it will be possible to better check the compliance of the code with the requirements, due to the fact that it will be easier to find what the given application or library provides (since the functionality will be available in the form of meaning markup), due to the fact that you can make more explicit causal chains for both the application logic and the one that led to the error.
As for the dualism of uncertainty and determinism, perhaps we need to stop trying to reconcile the incompatible. The work of any application can be represented in the form of (a) tightly coupled, statically typed code (with increased requirements for reliability, security, efficiency for specific scenarios) and (b) loosely coupled, dynamically typed and component constructions of a natural language (without reliability, security, which should be regulated by application level, and which can be flexibly applied for a wide range of scenarios). Is it worth trying to make more flexible constructs that are optimized for determinism? Is it worth trying to do more productive designs that focus more on uncertainty? Most likely not and no. Applications must be highly specialized, natural language adapt to any conditions, to each his own.
Source: https://habr.com/ru/post/318784/
All Articles