You can use dicts!
See also dict_options/2
?- option(foo(X),_{foo:bar},baz). X = bar. ?- option(foo(X),_{quux:bar},baz). X = baz.
Explainer
The default is the value carried by the option, not the whole option.
Basically:
- go through the OptionList until you find an element that "matches" Option, where "matches" does not mean unification but "has the same functor name". This is deterministic, in the sense that the search is broken off on the first match encountered. No alternatives are explored.
- if such en element is found, succeed (and the bindings allow the caller to extract the option value)
- if no such element exists, unify the first argument of Option with Default
If Option is not a compound term or of arity 0, an exception is raised. No special precautions are performed for Option an unbound variable or an open list. That's fine!
If Option is a compound term with higher arity, multiple option values can be processed in one go.
Why it's not unification (as would be done in memberchk) but first-occurence-wins functor matching:
?- option(b(2),[a(1),b(2),b(3),c(4)],4). % match b(2) which comes first true. ?- option(b(X),[a(1),b(2),b(3),c(4)],4). % match b(2) which comes first X = 2. ?- option(b(3),[a(1),b(2),b(3),c(4)],4). % do NOT match b(3) false.
Running it "backwards"?
One could imagine a generator predicate accepting options in order to form data as output, and then working "backwards" to accept some data as input, and building the options list needed to generate that data - in that case the options list would have to be an uninstantiated variable or at least the option values would have to be uninstantiated variables. That would be a more general way of using the options list (it becomes a real side-channel and would have to be marked as having mode '?' in the predicate signature)
Doc needs help
You can use SWI-Prolog dicts:
?- option(foo(X),_{foo:bar},quux). X = bar. ?- option(foo(X),_{zounds:bar},quux). X = quux.
Multiple values are (sometimes) not a problem:
?- option(foo(X,Y,Z),[foo(a,b,c)],quux). X = a, Y = b, Z = c. ?- option(foo(X,g,Z),[foo(a,h,c)],quux). false.
But what is the default for "multiple values"?
?- option(foo(X,Y,Z),[foo(a)],quux). X = quux. ?- option(foo(A,B,C),[],[alfa,g,beta]). A = [alfa, g, beta].
Examples
Option is foo(X)
and is matched against an empty list and the default to use is none
.
So X is bound to none
:
?- option(foo(X),[],none). X = none.
Do not specify the whole term expected to be in the option list as default:
?- option(foo(X),[],foo(none)). X = foo(none).
As expected, if an option carrying a value exists, that value is used instead:
?- option(foo(X),[foo(yes)],none). X = yes.
You can perform verification:
?- option(foo(yes),[foo(yes)],none). true.
A verification that makes option/3 actually fail - the Option is not in the OptionList and its value is not the default:
?- option(foo(yes),[],none). false.
A verification that makes it succeed - the Option is not in the OptionList but its value is actually the default:
?- option(foo(yes),[],yes). true.
This leads to compact code like:
(option(foo(yes),Options,yes)) -> yes_action ; no_action)
A bit weirdish, if the value is unbound, but expected due to unification:
?- option(foo(yes),[foo(Z)],none). Z = yes.
Create a cyclic term:
?- option(f(X),[],f(X)). X = f(X).
Code trickery:
Suppose you have an option what_text/1 to express whether the output should be atom
or string
, and it should be by default atom
. If the caller passes an open list or an unbound variable as OptionList, the captured Option may not be what you expect (it will be an unbound variable). On the other hand, if there is an unknown value, different from atom
or string
and you want to silently fall back to the default in that case (instead of throwing, but as usual, "good" behaviour of the predicate is meaningful only in a greater context, never by itself).
Here is an example, perform the non-defaults first to handle "default and unknown" into the same case:
transform(Chars,Text,Options) :- option(what_text(W),Options,atom), % capture value of option what_text in W; won't fail if_then_else( (W==string), % non-standard case first! string was ordered string_chars(Text,Chars), % perform non-standard case atom_chars(Text,Chars)). % default case or unknown case: % W==atom or something else
with
if_then_else(Condition,Then,Else) :- call(Condition) -> call(Then) ; call(Else).
for readability.