I've been trying to learn Prolog and this particular thing has been bugging me for a while: to get anything done, my query need to be shockingly long. In particular, I have the following snippet that represents a simple classification program:
% Define the objects
object(pencil, stationery).
object(apple, organic).
object(eraser, stationery).
object(book, stationery).
object(mouse, electronic).
object(mp3, electronic).
object(ipad, electronic).
% Define the baskets
basket(red_basket, electronic).
basket(tall_basket, stationery).
basket(round_basket, organic).
% Define the sorting rule
sort_object(Object, Basket) :-
object(Object, Type),
basket(Basket, Type).
% Query the program to sort the objects into the baskets
% How can we reduce this last statement to be MUCH shorter?
?- sort_object(pencil, Basket1), sort_object(apple, Basket2), sort_object(eraser, Basket3), sort_object(book, Basket4), sort_object(mouse, Basket5), sort_object(mp3, Basket6), sort_object(ipad, Basket7).
I like it that all the term definitions are as succinct as they can be, but the final query is WAYYYYYYY TOOOO LONG to my liking and feels wrong. In contrary, if we were to implement the same thing in Elixir, one might do:
defmodule DB do
def objects do
[:pencil, :iPad, :basketball, :laptop, :apple]
end
end
defmodule ObjectHelper do
def get_type(:pencil), do: :stationery
def get_type(:iPad), do: :electronics
def get_type(:basketball), do: :sport
def get_type(:laptop), do: :electronics
def get_type(:apple), do: :food
end
defmodule BasketHelper do
def get_basket(:electronics), do: :red
def get_basket(:stationery), do: :yellow
def get_basket(_), do: :blue
end
DB.objects
|> Enum.group_by(fn o -> o |> ObjectHelper.get_type |> BasketHelper.get_basket end)
|> IO.inspect
The goal is to output the correct baskets for each object, and you can envision doing that easily in C++ or Python cleanly albeit with longer code.
My question is:
I am using SWI Prolog.
Thanks!
First option is findall/3
(or bagof/3
) to find all solutions to a goal and gather up the results according to a template. For this, an Object and a Basket are a pair of things and a common way to pair two things in Prolog is A-B
, a term of two things joined with a hyphen:
?- findall(O-B, sort_object(O, B), Pairs).
Pairs = [pencil-tall_basket, apple-round_basket, eraser-tall_basket,
book-tall_basket, mouse-red_basket, mp3-red_basket, ipad-red_basket]
Second option is a less pure 'failure driven loop' to search, print the result, then fail the search and trigger backtracking to retry the search:
?- sort_object(O, B),
format('Thing: ~w, Basket: ~w,~n', [O,B]),
false.
Thing: pencil. Basket: tall_basket.
Thing: apple. Basket: round_basket.
Thing: eraser. Basket: tall_basket.
Thing: book. Basket: tall_basket.
Thing: mouse. Basket: red_basket.
Thing: mp3. Basket: red_basket.
Thing: ipad. Basket: red_basket.
false
but that doesn't give you the items to work with in your program.
Third option if you want the items grouped, SWI Prolog has group_by/4
which finds the groups on backtracking:
?- group_by(B, O, sort_object(O, B), Items).
B = red_basket,
Items = [mouse, mp3, ipad] ;
B = round_basket,
Items = [apple] ;
B = tall_basket,
Items = [pencil, eraser, book]
and that can be wrapped in findall as well:
?- findall(B-Items,
group_by(B, O, sort_object(O, B), Items),
AllGroups).
AllGroups = [red_basket-[mouse, mp3, ipad],
round_basket-[apple],
tall_basket-[pencil, eraser, book]]