Lenses in Prolog via DCG, possible or not? - functional-programming

Was playing around with lenses in Prolog. Lenses are a kind of microscope that allow to zoom into a structure and do some reads or writes in a functional fashion. Basically my point of departure was the following modelling of setters and declarative getters in Prolog:
Getter: Just a <closure>,
called as call(<closure>, X, Y),
this will retrieve the value Y from X.
Declarative Setter: The same <closure> but used with a different arity,
called as call(<closure>, X, Y, Z), this will update the X by a new value Y giving a new Z.
I quickly arrived at a definition of a lens composition operator #, which can be used to combine two lenses into a new one, just based on their closures. An example and a definition is found in the appendix. But according to this article lenses can be made to be simply compositional.
In my opinion, when something is compositional it can be easily modelled via DCG. I can do this for the getter as follows, but I did not yet figure out a way to do it for the declarative setter as well:
/* Getter composition as DCG */
#(C1, C2) -->
call(C1),
call(C2).
How would I model the setter composition in DCG? Is this possible, maybe altering the intial assumptions of how the getters and declarative setters are modelled, so that the result is simply compositional?
Best Regards
Appendix:
Here is an example of some setters and getters:
/* getter */
back(bicycle(X, _), X).
front(bicycle(_, Y), Y).
circumference(wheel(X, _), X).
spokes(wheel(_, Y), Y).
/* setter */
back(bicycle(_, Y), X, bicycle(X, Y)).
front(bicycle(X, _), Y, bicycle(X, Y)).
circumference(wheel(_, Y), X, wheel(X, Y)).
spokes(wheel(X, _), Y, wheel(X, Y)).
Here is the modelling of the lens composition:
:- op(600, xfy, #).
/* getter composition */
#(C1, C2, X, Y) :-
call(C1, X, H),
call(C2, H, Y).
/* setter composition */
#(C1, C2, X, Y, Z) :-
call(C1, X, H),
call(C2, H, Y, J),
call(C1, X, J, Z).
Here is are some example runs:
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.16)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
?- call(front#spokes, bicycle(wheel(1330, 12), wheel(1440, 16)), X).
X = 16.
6 ?- call(back#circumference, bicycle(wheel(1330, 12), wheel(1440, 16)), X).
X = 1330.
7 ?- call(front#circumference, bicycle(wheel(1330, 12), wheel(1440, 16)), 1420, X).
X = bicycle(wheel(1330, 12), wheel(1420, 16)).

There is a solution which has a further indirection, but which has also the potential to somehow precompile access paths. We view access paths again as closures, but this time they modify continuation functions. There are two types of continuation:
Getter Continuation:
- Transforms the value before returning it.
Declarative Setter Continuation:
- Transforms the value by an additional parameter before updating it.
An access path element now transforms the two continuations, to give a new continuation. The access path element transformation is compositional, whereby the inner access elements are first applied.
Some example continuations are given in the appendix, we can then model composition of access elements via DCG as follows. Note the order in the body of the DCG, this reflects aforementioned access elements order.
/* composition */
#(C1, C2) :-
call(C2),
call(C1).
Bye
Appendix:
Here are some transformer definitions:
/* transformer construction */
back(F, back(F)).
front(F, front(F)).
circumference(F, circumference(F)).
spokes(F, spokes(F)).
/* getter transformer */
back(F, bicycle(X, _), Y) :- call(F,X,Y).
front(F, bicycle(_, X), Y) :- call(F,X,Y).
circumference(F, wheel(X, _), Y) :- call(F,X,Y).
spokes(F, wheel(_, X), Y) :- call(F,X,Y).
/* setter transformer */
back(F, bicycle(X, Y), Z, bicycle(T, Y)) :- call(F,X,Z,T).
front(F, bicycle(X, Y), Z, bicycle(X, T)) :- call(F,Y,Z,T).
circumference(F, wheel(X, Y), Z, wheel(T, Y)) :- call(F,X,Z,T).
spokes(F, wheel(X, Y), Z, wheel(X, T)) :- call(F,Y,Z,T).
Here is the lens composition and some stop transformer:
:- op(600, xfy, #).
/* composition */
#(C1, C2, F, G) :-
call(C2, F, H),
call(C1, H, G).
/* stop getter */
id(X,X).
/* stop setter */
id(_,X,X).
here are some example runs:
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.14-1-ga20f192)
Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam
?- ['lens.pro'].
?- call(front#spokes, id, F), call(F, bicycle(wheel(1330, 12), wheel(1440, 16)), X).
F = front(spokes(id)),
X = 16.
?- call(back#circumference, id, F), call(F, bicycle(wheel(1330, 12), wheel(1440, 16)), X).
F = back(circumference(id)),
X = 1330.
?- call(front#circumference, id, F), call(F, bicycle(wheel(1330, 12), wheel(1440, 16)), 1420, X).
F = front(circumference(id)),
X = bicycle(wheel(1330, 12), wheel(1420, 16)).

Related

function composition for multiple arguments and nested functions

I have a pure function that takes 18 arguments process them and returns an answer.
Inside this function I call many other pure functions and those functions call other pure functions within them as deep as 6 levels.
This way of composition is cumbersome to test as the top level functions,in addition to their logic,have to gather parameters for inner functions.
# Minimal conceptual example
main_function(a, b, c, d, e) = begin
x = pure_function_1(a, b, d)
y = pure_function_2(a, c, e, x)
z = pure_function_3(b, c, y, x)
answer = pure_function_4(x,y,z)
return answer
end
# real example
calculate_time_dependant_losses(
Ap,
u,
Ac,
e,
Ic,
Ep,
Ecm_t,
fck,
RH,
T,
cementClass::Char,
ρ_1000,
σ_p_start,
f_pk,
t0,
ts,
t_start,
t_end,
) = begin
μ = σ_p_start / f_pk
fcm = fck + 8
Fr = σ_p_start * Ap
_σ_pb = σ_pb(Fr, Ac, e, Ic)
_ϵ_cs_t_start_t_end = ϵ_cs_ti_tj(ts, t_start, t_end, Ac, u, fck, RH, cementClass)
_ϕ_t0_t_start_t_end = ϕ_t0_ti_tj(RH, fcm, Ac, u, T, cementClass, t0, t_start, t_end)
_Δσ_pr_t_start_t_end =
Δσ_pr(σ_p_start, ρ_1000, t_end, μ) - Δσ_pr(σ_p_start, ρ_1000, t_start, μ)
denominator =
1 +
(1 + 0.8 * _ϕ_t0_t_start_t_end) * (1 + (Ac * e^2) / Ic) * ((Ep * Ap) / (Ecm_t * Ac))
shrinkageLoss = (_ϵ_cs_t_start_t_end * Ep) / denominator
relaxationLoss = (0.8 * _Δσ_pr_t_start_t_end) / denominator
creepLoss = (Ep * _ϕ_t0_t_start_t_end * _σ_pb) / Ecm_t / denominator
return shrinkageLoss + relaxationLoss + creepLoss
end
I see examples of functional composition (dot chaining,pipe operator etc) with single argument functions.
Is it practical to compose the above function using functional programming?If yes, how?
The standard and simple way is to recast your example so that it can be written as
# Minimal conceptual example, re-cast
main_function(a, b, c, d, e) = begin
x = pure_function_1'(a, b, d)()
y = pure_function_2'(a, c, e)(x)
z = pure_function_3'(b, c)(y) // I presume you meant `y` here
answer = pure_function_4(z) // and here, z
return answer
end
Meaning, we use functions that return functions of one argument. Now these functions can be easily composed, using e.g. a forward-composition operator (f >>> g)(x) = g(f(x)) :
# Minimal conceptual example, re-cast, composed
main_function(a, b, c, d, e) = begin
composed_calculation =
pure_function_1'(a, b, d) >>>
pure_function_2'(a, c, e) >>>
pure_function_3'(b, c, y) >>>
pure_function_4
answer = composed_calculation()
return answer
end
If you really need the various x y and z at differing points in time during the composed computation, you can pass them around in a compound, record-like data structure. We can avoid the coupling of this argument handling if we have extensible records:
# Minimal conceptual example, re-cast, composed, args packaged
main_function(a, b, c, d, e) = begin
composed_calculation =
pure_function_1'(a, b, d) >>> put('x') >>>
get('x') >>> pure_function_2'(a, c, e) >>> put('y') >>>
get('x') >>> pure_function_3'(b, c, y) >>> put('z') >>>
get({'x';'y';'z'}) >>> pure_function_4
answer = composed_calculation(empty_initial_state)
return value(answer)
end
The passed around "state" would be comprised of two fields: a value and an extensible record. The functions would accept this state, use the value as their additional input, and leave the record unchanged. get would take the specified field out of the record and put it in the "value" field in the state. put would mutate the extensible record in the state:
put(field_name) = ( {value:v ; record:r} =>
{v ; put_record_field( r, field_name, v)} )
get(field_name) = ( {value:v ; record:r} =>
{get_record_field( r, field_name) ; r} )
pure_function_2'(a, c, e) = ( {value:v ; record:r} =>
{pure_function_2(a, c, e, v); r} )
value(r) = get_record_field( r, value)
empty_initial_state = { novalue ; empty_record }
All in pseudocode.
Augmented function application, and hence composition, is one way of thinking about "what monads are". Passing around the pairing of a produced/expected argument and a state is known as State Monad. The coder focuses on dealing with the values while treating the state as if "hidden" "under wraps", as we do here through the get/put etc. facilities. Under this illusion/abstraction, we do get to "simply" compose our functions.
I can make a small start at the end:
sum $ map (/ denominator)
[ _ϵ_cs_t_start_t_end * Ep
, 0.8 * _Δσ_pr_t_start_t_end
, (Ep * _ϕ_t0_t_start_t_end * _σ_pb) / Ecm_t
]
As mentioned in the comments (repeatedly), the function composition operator does indeed accept multiple argument functions. Cite: https://docs.julialang.org/en/v1/base/base/#Base.:%E2%88%98
help?> ∘
"∘" can be typed by \circ<tab>
search: ∘
f ∘ g
Compose functions: i.e. (f ∘ g)(args...; kwargs...) means f(g(args...; kwargs...)). The ∘ symbol
can be entered in the Julia REPL (and most editors, appropriately configured) by typing
\circ<tab>.
Function composition also works in prefix form: ∘(f, g) is the same as f ∘ g. The prefix form
supports composition of multiple functions: ∘(f, g, h) = f ∘ g ∘ h and splatting ∘(fs...) for
composing an iterable collection of functions.
The challenge is chaining the operations together, because any function can only pass on a tuple to the next function in the composed chain. The solution could be making sure your chained functions 'splat' the input tuples into the next function.
Example:
# splat to turn max into a tuple-accepting function
julia> f = (x->max(x...)) ∘ minmax;
julia> f(3,5)
5
Using this will in no way help make your function cleaner, though, in fact it will probably make a horrible mess.
Your problems do not at all seem to me to be related to how you call, chain or compose your functions, but are entirely due to not organizing the inputs in reasonable types with clean interfaces.
Edit: Here's a custom composition operator that splats arguments, to avoid the tuple output issue, though I don't see how it can help picking the right arguments, it just passes everything on:
⊕(f, g) = (args...) -> f(g(args...)...)
⊕(f, g, h...) = ⊕(f, ⊕(g, h...))
Example:
julia> myrev(x...) = reverse(x);
julia> (myrev ⊕ minmax)(5,7)
(7, 5)
julia> (minmax ⊕ myrev ⊕ minmax)(5,7)
(5, 7)

Meta all introduction in Isabelle

I'm confused about the all introduction meta rule in Isabelle. The papers say it should be:
From P deduce ⋀ x. P whenever x is not a free variables in the asumptions.
This is confusing to me. I understand better wikipedia's one:
From (P y) deduce ⋀ x. P x whenever y is not free in the (implicit) assumptions and x is not free in P.
How is the meta-forall rule encoded in Isabelle? Here is the source code:
(*Forall introduction. The Free or Var x must not be free in the hypotheses.
[x]
:
A
------
⋀x. A
*)
fun forall_intr
(ct as Cterm {maxidx = maxidx1, t = x, T, sorts, ...})
(th as Thm (der, {maxidx = maxidx2, shyps, hyps, tpairs, prop, ...})) =
let
fun result a =
Thm (deriv_rule1 (Proofterm.forall_intr_proof x a) der,
{cert = join_certificate1 (ct, th),
tags = [],
maxidx = Int.max (maxidx1, maxidx2),
shyps = Sorts.union sorts shyps,
hyps = hyps,
tpairs = tpairs,
prop = Logic.all_const T $ Abs (a, T, abstract_over (x, prop))});
fun check_occs a x ts =
if exists (fn t => Logic.occs (x, t)) ts then
raise THM ("forall_intr: variable " ^ quote a ^ " free in assumptions", 0, [th])
else ();
in
(case x of
Free (a, _) => (check_occs a x hyps; check_occs a x (terms_of_tpairs tpairs); result a)
| Var ((a, _), _) => (check_occs a x (terms_of_tpairs tpairs); result a)
| _ => raise THM ("forall_intr: not a variable", 0, [th]))
end;
Suppose I am a mathematician with only some notions of programming. How would you convince me the piece of code below implements the meta-forall rule in a sensible manner?.

Why does my prolog rule get stuck in infinite recursion

My code works for its intended purpose but always gets stuck in a loop at the end giving me an error saying "Stack limit exceeded." My code is below:
byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).
byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).
byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).
travel(X,Y):- byCar(X,Y).
travel(X,Y):- byTrain(X,Y).
travel(X,Y):- byPlane(X,Y).
travel(X,Y):- travel(X,Z), travel(Z,Y).
When you call something like travel(metz, To), the last clause of travel/2 will call travel(metz, Z) with a new variable Z, which can then call travel(metz, Z2) with a new variable Z2, and so on.
This problem is called "left recursion": You have a recursive call that is equivalent to the original goal all the way "to the left" (i.e., at the beginning) of a clause. The solution is to "make some progress" before a recursive call. In this case, you can travel one hop before the recursion:
step(X, Y) :-
byCar(X, Y).
step(X, Y) :-
byTrain(X, Y).
step(X, Y) :-
byPlane(X, Y).
travel(X, Y) :-
step(X, Y).
travel(X, Z) :-
step(X, Y),
travel(Y, Z).
This now terminates:
?- travel(metz, To).
To = frankfurt ;
To = paris ;
To = bangkok ;
To = singapore ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = losAngeles ;
To = auckland ;
To = hamilton ;
To = raglan ;
false.
As pointed out in a comment by false, you can use a general predicate to capture this kind of closure: Definition of Reflexive Transitive Closure. Alternatively, some Prolog systems provide a feature called "tabling" which you can use to avoid this kind of problem: https://www.swi-prolog.org/pldoc/man?section=tabling-non-termination

Checking descendants of a family tree in Prolog

How can I recursively check if X is the descendent of Y, using a recursive method descendent_of? The fact base consists of males and females, and father_of/mother_of statements.
/* Facts */
male(roy).
male(lee).
...
female(joy).
female(ana).
...
/* Rules */
grandmother_of(X, Z) :-
mother_of(X, Y),
(mother_of(Y, Z);
father_of(Y, Z)).
grandfather_of(X, Z) :-
father_of(X, Y),
(mother_of(Y, Z);
father_of(Y, Z)).
parent_of(X,Y) :-
mother_of(X,Y);father_of(X,Y).
descendent_of(X,Y) :-
*/ Recursive method here */
How do I set up the stopping condition? Also, how does recursion work in prolog if functions only return boolean values?
Any comments/suggestions are greatly appreciated.
If you look at descendant_of(X,Y) it should be true if X is descendant of Y. The truth value is than reursivly defined as follows:
I do here a little trick to achieve consistency with parent_of and introduce the predicate ancestor_of/2 as
descendent_of(X,Y) :- ancestor_of(Y,X)
Know X is ancestor of Y if either
X is parent of Y or
X is parent of Z and Z is descendant of Y
(1) is the base case of the recursion and (2) is the recursive definition.
Hint: You could also redefine the recursive case as: Z is descendant of Y and X is parent of Z (Keyword tail-recursion)
I've extended your facts to give some working data.
/* Facts */
male(roy).
male(lee).
female(joy).
female(ana).
parent_of(roy,joy).
parent_of(joy,ana).
parent_of(lee,ana).
Now we can build some rules. Let's start with grand_parent_of/2.
grand_parent_of(X,Z) :-
parent_of(X,Y),
parent_of(Y,Z).
This keeps the rules for grand_father_of/2 and grand_mother_of/2 rather simple.
grand_father_of(X,Y) :-
male(X),
grand_parent_of(X,Y).
grand_mother_of(X,Y) :-
female(X),
grand_parent_of(X,Y).
Now, every predicate so far the ancestor has been on the left and the descendent on the right, so to keep consistency, let's define ancestor_of/2 first, rather than descendent_of/2.
ancestor_of(X,Y) :-
parent_of(X,Y).
ancestor_of(X,Y) :-
parent_of(X,Z),
ancestor_of(Z,Y).
Note that the second predicate is recursive.
Now we can easily define descendent_of/2 simply in terms of ancestor_of/2.
descendent_of(X,Y) :-
ancestor_of(Y,X).
Here are the results of running the following query and asking prolog to keep giving me results.
?- descendent_of(X,Y).
X = joy,
Y = roy ;
X = ana,
Y = joy ;
X = ana,
Y = lee ;
X = ana,
Y = roy ;
false.

Recursion in prolog using append

I'm new in Prolog and I have some problem understanding how the recursion works.
The think I want to do is to create a list of numbers (to later draw a graphic).
So I have this code :
nbClassTest(0, _).
nbClassTest(X, L) :-
numberTestClass(A,X),
append([A], L, L),
X is X - 1,
nbClassTest(X, L).
But it keeps giving me 'false' as an answer and I don't understand why it doesn't fill the list. It should end if X reaches 0 right?
The numberTestClass(A,X), gives me a number (in the variable A) for some X as if it was a function.
You should build the list without appending, because it's rather inefficient.
This code could do:
nbClassTest(0, []).
nbClassTest(X, [A|R]) :-
numberTestClass(A, X),
X is X - 1,
nbClassTest(X, R).
or, if your system has between/3, you can use an 'all solutions' idiom:
nbClassTest(X, L) :-
findall(A, (between(1, X, N), numberTestClass(A, X)), R),
reverse(R, L).
the problem is that you use the same variable for the old and the new list. right now your first to append/3 creates a list of infinite length consisting of elements equal to the value of A.
?-append([42],L,L).
L = [42|L].
?- append([42],L,L), [A,B,C,D|E]=L.
L = [42|L],
A = B, B = C, C = D, D = 42,
E = [42|L].
then, if the next A is not the same with the previous A it will fail.
?- append([42],L,L), append([41],L,L).
false.
there is still on more issue with the code; your base case has an non-instantiated variable. you might want that but i believe that you actually want an empty list:
nbClassTest(0, []).
nbClassTest(X, L) :-
numberTestClass(A,X),
append([A], L, NL),
X is X - 1,
nbClassTest(X, NL).
last, append/3 is kinda inefficient so you might want to avoid it and build the list the other way around (or use difference lists)
It fails because you use append in wrong way
try
nbClassTest(0, _).
nbClassTest(X, L) :-
numberTestClass(A,X),
append([A], L, Nl),
X is X - 1,
nbClassTest(X, Nl).
append concatenate 2 lists so there is no such list which after adding to it element still will be same list.

Resources