# Option preference (first or last)?

**URL:** <https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742>\
**Category:** Discussion\
**Created:** [July 14, 2026, 2:53pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742 "2026-07-14T14:53:34Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [July 14, 2026, 2:53pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/1 "2026-07-14T14:53:34Z")

</div>

This is a followup for [Minor: "prolog flag option lists" do not follow option list semantics · Issue #756 · SWI-Prolog/swipl-devel · GitHub](https://github.com/SWI-Prolog/swipl-devel/issues/756)

It appears ISO talks about duplicate/conflicting options in a few places. Where it does, it says the **rightmost** (last) wins. That is also what SWI-Prolog implements in PL\_scan\_options() which is used by pretty much any predicate defined in C(++).

Prolog predicates tended to use member/2, memberchk/2 and later SWI-Prolog’s option/2 and friends. The latter deals with options written `Key(Value)` as well as `Key = Value`. It also deals with lists as well as dicts. For lists, this takes the **leftmost** as winner.

This discrepancy is surely not desirable. The most obvious solution is to make library(options) compatible with PL\_scan\_options(). However, at last quite a bit of my code uses constructs like this:

```prolog
p(..., Options) :-
    q(..., [key(Value)|Options]).

```

Note that library(options) provides merge\_options/3, so one could/should write

```prolog
p(..., Options) :-
    merge_options([key(Value)], Options, Options1),
    q(..., Options1).

```

Now, `Options1` will have `key(Value)`, regardless of whether or not there is a `key(OtherValue)` in `Options` or not.

How should this be resolved? Related, is using `[key(Value)|Options]` a template that is in wide use?

---

<div class="post-metadata">

**Author:** ![swi](https://avatars.discourse-cdn.com/v4/letter/s/51bf81/32.png) [@swi](https://swi-prolog.discourse.group/u/swi)\
**Post date:** [July 14, 2026, 3:01pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/2 "2026-07-14T15:01:40Z")

</div>

> [@jan](#):
>
> Related, is using `[key(Value)|Options]` a template that is in wide use?

I use it in quite a bit of code; unfortunately I can’t think of a good solution that doesn’t break everything.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [July 14, 2026, 3:18pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/3 "2026-07-14T15:18:19Z")

</div>

> [@swi](#):
>
> good solution that doesn’t break everything.

Worse, I had Claude code do a quick scan on the whole SWI-Prolog source tree. It uses `[key(Value)|Options]` in 21 places (probably missed some). All seem to expect to overrule a possibly existing option, but a couple call a C predicate and thus behave differently (read: not as intended) ☹

Claude proposes a Prolog flag `option_duplicates` with values `first|last|warning|error`. That makes some sense, but you would like to have it scoped so you can say “this file assumes left (or right) wins”, but this should affect the file where you manipulate the option list, while the processing happens elsewhere ☹ Multiple users of some library may have different intends ☹

Once I was young, not spotting any dangers 🙂 I fear “to be continued” … Ideas are welcome!

---

<div class="post-metadata">

**Author:** ![jfmc](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jfmc/32/3308_2.png) [@jfmc](https://swi-prolog.discourse.group/u/jfmc)\
**Post date:** [July 14, 2026, 3:19pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/4 "2026-07-14T15:19:40Z")

</div>

Languages with (stablished) dictionary syntax use record-update syntax for that (e.g., `{ ...opts, key: val }`). That does a shallow copy of the whole dictionary, which is equivalent to `merge_options/3`. I’d prefer if option lists with duplicated keys are left as ‘implementation defined’ or ‘undefined behavior’ such that programmers cannot assume what will happen and program defensively here (do not duplicate keys, it is confusing… key/values should be order independent and with rightmost or leftmost win, it is no longer true).

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [July 14, 2026, 4:19pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/5 "2026-07-14T16:19:55Z")

</div>

> [@jfmc](#):
>
> I’d prefer if option lists with duplicated keys are left as ‘implementation defined’ or ‘undefined behavior’

Disagree. It’s fairly common in code that I’ve seen (and I think that this is how most Unix commands work) to set up an initial set of defaults and then process the user’s list, to override the defaults.

Both options(last) and options(first) derive their semantics from the simplest implementation (for options(last), Prolog would require an additional call to append/3; for Unix commands, one simply loops through the options and does a `switch` on them).

One advantage of the Unix way is that it easily works with shell scripts. For example, I can do something like:  
`my_cmd -a1 -b2 "$@"`  
(my shell-fu is old, so I might have this wrong).  
Of course, it would be just as easy to do `my_cmd "$@" -a1 -b2` but that ship has sailed.

Anyway, one thought about how to accommodate both options(first) and options(last) is to change predicates that take a single `Options` argument but two arguments: `Options,DefaultOptions` and have the code do merge\_options/3. Alternatively, `Options,OptionPrecedence`, where `OptionPrecedence` is one of `first`, `last`, `warning(Pred)`, `error(Pred)`.

(Options(warning) is best left to a separate discussion, I think.)

PS: I’ve never really liked options(first), probably because I’ve become used to options(last) from how most software (that I’ve used) works.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [July 15, 2026, 7:55am UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/6 "2026-07-15T07:55:53Z")

</div>

> [@peter.ludemann](#):
>
> PS: I’ve never really liked options(first),

I think we all agree that was a mistake. What is at stake here is whether there is a sensible route to get this right in the end or not? I think the best way out is to migrate to dicts, which are a lot faster and have clear semantics. Now you overrule options using `Old.put(#{k:v, ...})` or you add new defaults using  
`#{k:v, ...}.put(Old)`. But, this moves too far away from de-facto standard Prolog ☹

> [@peter.ludemann](#):
>
> `Options` argument but two arguments: `Options,DefaultOptions`

I doubt this solves the problem (e.g., how do you chain this?). There might be an option in allowing for terms that combine options lists though. For example, allow for a term `merge(Opts1, Opts2)`, that will combine the option lists in a well defined way. Now, the name _merge_ is bad as it does not tell which one wins (I always forget the argument order of merge\_options/3), but the idea stands. Options need to be processed by a library anyway as member/2, etc. take the wrong leftmost wins.

Note that this could also lead to e.g. (bad names) first(List) or last(List), so you can overrule an option using `first([k(v)|Opts)])`.

Could something based on these ideas, together with (incomplete) static analysis and warnings/errors in the option processing library be a way out?

---

<div class="post-metadata">

**Author:** ![peter.ludemann](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/peter.ludemann/32/48_2.png) [@peter.ludemann](https://swi-prolog.discourse.group/u/peter.ludemann)\
**Post date:** [July 15, 2026, 5:31pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/7 "2026-07-15T17:31:20Z")

</div>

> [@jan](#):
>
> I doubt this solves the problem (e.g., how do you chain this?).

`[k(v)|Opts]` in the current design would become `Opts,[k(v)]` with my `Options,DefaultOptions` suggestion.

> [@jan](#):
>
> first([k(v)|Opts)])

This would work (also with `last`, `warning`, `error`, `handle`; where `hook` provides a predicate to predicate to handle errors)

> [@jan](#):
>
> together with (incomplete) static analysis

static analysis is something I’ve wanted to work on (and also type inferencing) but keep on getting sidetracked. What are the best static analysis tools these days?

---

<div class="post-metadata">

**Author:** ![nicos](https://avatars.discourse-cdn.com/v4/letter/n/7feea3/32.png) [@nicos](https://swi-prolog.discourse.group/u/nicos)\
**Post date:** [July 16, 2026, 10:11am UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/8 "2026-07-16T10:11:21Z")

</div>

Dicitionaries would make the code harder for the average population.  
I was recently looking at the pack installer, which I think uses dicts, and it was much harder to figure out what was going on.

pack(options) takes the leftmost but it is flexible in allowing options that can appear multiple times.

I did n’t do extensive research when coding pack(options). My intuition probably was that  
optionised predicates most times/options have defaults. As lists are normally added to at the head then it seemed natural that invocation options will be added to the left.  
Leftmost on a list is also most efficient when looking at lists to pick up the first matching term.

## Nicos Angelopoulos

> **[Nicos Angelopoulos](https://stoics.org.uk/~nicos/)**
>
> logic programming

pack(options):  
["options" pack for SWI-Prolog](https://eu.swi-prolog.org/pack/list?p=options)  
[options: Options handling.](http://stoics.org.uk/~nicos/sware/options)

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [July 16, 2026, 12:52pm UTC](https://swi-prolog.discourse.group/t/option-preference-first-or-last/9742/9 "2026-07-16T12:52:19Z")

</div>

> [@peter.ludemann](#):
>
> static analysis is something I’ve wanted to work on (and also type inferencing) but keep on getting sidetracked. What are the best static analysis tools these days?

I don’t know. This is mostly about data flow analysis such that you can figure out what is eventually passed to a predicate processing options. library(predicate\_options) provides a small start. At the moment, I’m using Claude code to

- Get rid of hand crafted option processing. This implies that Prolog code should use library(option), C code PL\_scan\_options(). This should have no or minimal impact. It mostly provides consistency (where C and Prolog code behave different, but that is what we are trying to resolve).
- Rewrite predicates that allow for repeated options to use a single option with a list value. This provides a compatibility and deprecated warning. For example archive\_open/4 allowed for multiple filter(+Filter) options. It now implements `filters(List)` and a small wrapper that maps this convention and emits a deprecated warning.
- Complete :- predicate\_options/3 declarations that allow check/0 (delegating to check\_predicate\_options/0) and the highlight library to flag unknown options or wrong values. It also tells us which built-ins and library predicates process options on what argument.
- Extend linter checking in `library(predicate_options)` to find patterns where options are combined and thus ordering can matter. Eventually, I want to get rid of direct list manipulation on option lists and provide the tooling that allows user code to do the same with little effort.

That should give a fair picture on the issues as well as improve static checking and improve the consistency of the system (dicts should work anywhere once this is complete).

> [@nicos](#):
>
> Dicitionaries would make the code harder for the average population.  
> I was recently looking at the pack installer, which I think uses dicts, and it was much harder to figure out what was going on.

The pack installer use library(option) exclusively. It does represent its knowledge about packs as dicts, i.e., knowledge about already installed packs, available packs and installation plans. I don’t see any representation that would have made this more readable. That said, the code has been developed bottom up for a long time and got more and more complicated ☹ Possibly it should be reconsidered … Especially when foreign code is involved though, packaging is complicated as I experienced trying to create the Janus pack for Python …
