Note in particular that you cannot backtrack over the list of options via option/2
Esentially option/2 behaves like memberchk/2 but demands that the 'Option' argument not be an uninstantiated variable.
This precludes detecting option repetition (as in [verbose(true),verbose(true)]) or options with multiple values (as in [file(foo),file(bar)]). You will have to implement this differently.
However, you can easily override "older" options with "newer" one added at the head of the Options list:
Expected, you get the "earliest b(X)":
?- option(b(X),[a(1),b(2),b(3),c(4)]). X = 2.
Maybe unexpected: "there is no b(3) ... or it is overriden by some other b(X)"
?- option(b(3),[a(1),b(2),b(3),c(4)]). false.
(Would it be nicer to allow backtracking?)
How it is done in (for example) venerable Perl: https://perldoc.perl.org/Getopt/Long.html#Options-with-multiple-values.
Fun with testing:
:- begin_tests(options).
test("Getting an option from an empty option-list fails as expected",fail) :-
option(b(_),[]).
test("Getting a option from an option-list that doesn't contain that option fails as expected",fail) :-
option(d(_),[a(1),b(2),c(3)]).
test("Leaving the 'Option' argument an unbound variable throws (instead of backtracking over all options)", [error(instantiation_error)]) :-
option(_,[a(1),b(2),c(3)]).
test("Normal way: Getting the argument of a b(X)",true(X == 2)) :-
option(b(X),[a(1),b(2),c(3)]).
test("Normal way: Getting the argument of a b(X), if there are multiple solutions, yields just one solution",true(Bag == [2])) :-
bagof(X, option(b(X),[a(1),b(2),b(3),c(3)]), Bag).
test("Normal way: Checking the presence of a ground b(x)",true) :-
option(b(2),[a(1),b(2),c(3)]).
% This is bad and should be changed!
test("Normal way: Checking the presence of a ground b(x) that comes after a b(y) unexpectedly fails",fail) :-
option(b(3),[a(1),b(2),b(3),c(4)]).
test("Normal way: Getting the argument of an option with a complex argument",true([X,Y,Z] == [1,2,3])) :-
option(b(x(X,Y,Z)),[a(1),b(x(1,2,3)),c(3)]).
test("What if the option argument in the list is a variable? Unification happens!",true(X == Y)) :-
option(b(X),[a(1),b(Y),c(3)]).
test("What if the option argument in the list is a variable (take 2)?",true(foo == Y)) :-
option(b(foo),[a(1),b(Y),c(3)]).
test("A duplicate option will yield a single entry (no backtracking)",true(Bag == [2])) :-
bagof(X, option(b(X),[a(1),b(2),b(3),c(4)]), Bag).
test("Checking the presence of an 'entry with no arguments' (an atom), which exists") :-
option(b,[a(1),b,c(4)]).
test("Checking the presence of an 'entry with no arguments' (an atom), which does not exist", fail) :-
option(b,[a(1),b(2),c(4)]).
test("Checking the presence of an 'entry with zero arguments' (an zero-arg compound), doesn't work", error(domain_error(_,_))) :-
option(b(),[a(1),b(),c(4)]).
test("The 'entry with zero arguments' can be in the list but won't be found", fail) :-
option(b,[a(1),b(),c(4)]).
test("Checking the presence of an 'entry with multiple arguments'", true([X,Y] == [2,5])) :-
option(b(X,Y),[a(1),b(2,5),c(4)]).
test("Checking the presence of an 'entry with multiple arguments', where the 'Option' argument is partially instantiated", true([X] == [2])) :-
option(b(X,5),[a(1),b(2,5),c(4)]).
test("Default value is used if the option is missing",true(X == false)) :-
option(foo(X),[],false).
test("Default value is not used if the option is there",true(X == true)) :-
option(foo(X),[foo(true)],false).
test("Complex default value is used if the option is missing",true(X == h(x,y))) :-
option(foo(X),[],h(x,y)).
test("Getting default value if one is just testing",fail) :-
option(foo(bar),[foo(true)],false).
test("Getting default value if one is just testing") :-
option(foo(bar),[foo(bar)],false).
:- end_tests(options).
See also
https://eu.swi-prolog.org/pldoc/man?section=predicate_options