Predicate that always returns true

Hi everyone.

I’m trying to build a network analyzer that uses SWI-Prolog as knowledge base.

I’m creating a predicate whose purpose is to check if a network is already present in the KB, otherwise it must perform other operation before adding it:

add_network(Host):-
    route(Host, Network, Subnet),
    (\+ network(Network, Subnet) ->
        % some_other_operations_with(Network)
    ).

Predicates route/3 and network/2 are asserted dynamically when new knowledge is obtained in other parts of the code.

Suppose KB already contains:

network(192.168.0.0, 24, 192.168.0.1).
network(192.168.2.0, 24, 192.168.2.1).
route(192.168.0.1, 192.168.0.0, eth0).
route(192.168.0.1, 192.168.1.0, eth1).
route(192.168.2.1, 192.168.2.0, eth0).

This means that host 192.168.0.1 is connected to 192.168.0.0/24 and has two interfaces, one of them connects him to a new network. And host 192.168.2.1 is connected to 192.168.2.0/24.
So the goal of the predicate is to do some operation only on 192.168.1.0/24.

The problem: during backtracking over networks, the result of this predicate is always the result of the last network tested (in this example 192.168.2.0 that makes the predicate return false).
Is there a way to make this predicate always return true even if didn’t find any new networks ?

Does it suffice to have a true else branch? e.g.

add_network(Host):-
    route(Host, Network, _),
    (\+ network(Network, Subnet, _) ->
        % some_other_operations_with(Network)
    ; true
    ).

Since, as the documentation for ->/2 states " Please note that (If -> Then) acts as (If -> Then ; fail), making the construct fail if the condition fails."

1 Like

Adding a true branch doesn’t work because backtracking would stop because the predicate is returning true after 192.168.0.0, which already exists in KB.

No it wouldn’t - there’s no cut to stop backtracking: !/0
Edit to clarify: Or anything else such as ->/2

Also: Having a route to a network does not necessarily mean that the network exists - so I don’t see the logic in:

add_network(Host):-
    route(Host, Network, _),

If you need to keep backtracking, you could try *->/2

->/2 does cut, actually, although with slightly different semantics when combined with ;/2.

1 Like

But there is a ->/2 in the second line that does cuts.

I know. In fact, that’s one of the operations that happens in % some_other_operations_with(Network).

This seems a bit clearer (I’ve never liked negative conditions in if-then-else, regardless of the programming language):

    ( network(Network, Subnet, _)
    -> true
    ;  % some_other_operation_with(Network)
    )

(\+)/1 can be tricky to get right if all the arguments aren’t ground. In your case, you seem to have two uninstantiated variables, Subnet and the anonymous _. Presumably you’re planning to use Subnet later in the predicate (otherwise you’d be getting a warning about Subnet being a singleton variable)? - but the (\+)/1 will undo any bindings, so the use of “not” seems wrong.

You might also want to try defining

network(Network, Subnet) :-
    network(Network, Subnet, _).

to get rid of the uninstantiated variable in your “not”. Or

network(Network) :-
    network(Network, _, _).

Another thing that’s worth considering:

    (    once(network(Network, Subnet, _))
    ;    % some_other_operation_with(Network)
    )
1 Like

I’ll try to make it more clear:
I need something that emulates the iteration through the arguments 192.168.0.0, 192.168.1.0 and 192.168.2.0.
For each one, checks if already exists and otherwise will perform some operation.
Finally, I need it to return always true.

Thanks for all the other advice.

I think once/1 won’t suit this scenario because it will not work with more than one network to be added to KB. Am I wrong ?

I try to avoid dynamic predicates – in this case, I might keep the information in an associative list (e.g., using dicts or red-black trees) – this maintains the declarative nature of the program.

An exception is if I’m loading some data and need to do additional processing on the data to get it into a suitable form. In that situation, I usually break my program into two parts – the first part processes the data and produces a new set of facts that can be used as-is, and the second part uses the generated data.

I can’t answer your question because I don’t understand your intent. Your code seems to be wrong because Subnet is uninstantiated when you call \\+ network(Network, Subnet, _) and it won’t become instantiated to a value in that situations (it’s essentially equivalent to \\+ network(Network _, _)).

You’re right, I’m sorry. Edited the original post.

route/3 instantiates Network and Subnet, which are then used in network/2 to check if network with that mask exists.

There is something missing – the code now shows a call to network/2 but the facts are network/3. Did you add a network/2 predicate, such as the one I suggested in Predicate that always returns true - #7 by peter.ludemann ?

To state my point again, slightly better:

Adding a network does not require that a route to it already exists. So this is incorrect logic:

add_network(Host):-
    route(Host, Network, Subnet),

Looks like it should be changed and renamed to:

network_exists(Network, Subnet) :-

Yes I did.

Also, in Predicate that always returns true - #8 by muppet39 I tried to make my intent more clear.

You’re right, but I don’t think it’s the main point of this topic.

Equally confused by what you’re trying to achieve but I’ll give it a go:

add_network(Host):-
    route(Host,Network, _Interface),
    \+ network(Network,_MaskLen,Host),
    writeln(network(Network, 24, Host)).
add_network(_).

add_network2(Host):-
    forall( (route(Host,Network, _Interface), \+ network(Network,_MaskLen,Host)),
	    writeln(network(Network, 24, Host))).

So, it finds (implied) non existent Network(s) for Host and then executes writeln (as a substitute for operation(…), maybe assertz for instance).
Second version assumes that operation() is side effects only.

Both will apply operation() to all implied non existent networks.
Use once(add_network(…)) to do just first.

Why would the Network variable come from route/3?

It might be more productive to ask:

  • what data do you have, and in what format?
  • what are you trying to compute?

(In other words, let’s do a basic data modelling of the problem and see what comes out of that.)