Discussion: Lets introduce a select statement?

Over here ELSEIF limitation in conditional syntax - #8 by Bob_Jansen there is an example of using the if/elseif statements as a case or select structure.

Given the elseifs do not demand closure it is easy to use if/elseif as a replacement for the more specific select statement. However if we had a <%select filter %> we could make it even more readable for select or case structures.

I admit I have being playing with a ChatGPT generated wikirule

If I implement this @Bob_Jansen’s solution it would be different but when I look at it there is other complexities. Lets put that aside for now.

  • note: It uses the ability for a filter to return multiple values and use limit or first to force only the first value. This is my opinionated view and how I always believed %if, functions and other aspects of tiddlywiki should have been implemented.

Proposal


Overview

<%select selector-filter %>

<%case literal-value %>
...

<%when subfilter %>
...

<%endselect%>

Core Concept (Important)

Unlike <%if%>, <%select%> does NOT collapse to a single value.

:backhand_index_pointing_right: The selector-filter may return multiple results, and each result is processed independently.

For each result:

<<value>>

is set, and all <%case%> / <%when%> clauses are evaluated.


Definitions

selector-filter

The filter in:

<%select ... %>

It may return:

  • 0 values → nothing renders
  • 1 value → standard select behaviour
  • N values → the entire select block is evaluated N times (once per value)

subfilter

The filter in:

<%when [subfilter] %>

It is applied to the current <<value>>.

So:

<%when [match[1]] %>

means:

[<value>match[1]]

case shorthand

<%case 1 %>

is equivalent to:

<%when [match[1]] %>

Nice compact test syntax

<$let valueList = "1 2">

<%select [<valueList>split[ ]] %>

<%case 1 %>
case 1: <<value>>

<%when [match[2]] %>
when 2: <<value>>

<%when [is[blank]] %>
blank: <<value>>

<%when [!is[blank]] %>
all values: <<value>>

<%endselect%>

</$let>

Expected behaviour

Because the selector-filter returns multiple values, the block runs once per value:

For value = 1

  • case 1 :white_check_mark:
  • !is[blank] :white_check_mark:

For value = 2

  • match[2] :white_check_mark:
  • !is[blank] :white_check_mark:

Output

case 1: 1
all values: 1
when 2: 2
all values: 2

Design Principle

<%if%>     → boolean (first result only)
<%select%> → iterable (all results)

This keeps <%select%> aligned with TiddlyWiki’s $list semantics and avoids introducing special cardinality rules.

If a single value is desired, it can be controlled explicitly:

<%select [tag[Project]first[]] %>

or:

<%select [tag[Project]limit[1]] %>

What do you think?

Notes;

I have a working example I would be happy to share but it is really just an experiment for now, I would not want it to escape into the wild yet.

It is clear from the way the %if and now the %select statement is implemented it is in many ways just a meta structure, ie it generates a set of nested list widgets to generate the required behaviour.

  • The key advantage as the author and reader is it presents something that is more complex in an easier to read syntax.

This makes me ask what other common needs could be implemented this way, perhaps via <%clauses%> however it seems this method does not provision the passing of values or parameters into the clause, it is possible to use defaults eg “counter=item” on the outer list widget. We can however set variables outside the statement that are made use of within the clause.

  • The only compelling one that comes to mind is a recursive structure as all the rest are fairly straightforward with filters.

I had to rewrite your code in $list/conditional syntax to really grasp what you were proposing here. I think this would be the current equivalent; please let me know if I’ve misunderstood.

<$let valueList="1 2">
<$list filter="[<valueList>split[ ]]" variable="value">
	<% if [<value>match[1]] %>
		when 1: <<value>>
	<% endif %>
	<% if [<value>match[2]] %>
		when 2: <<value>>
	<% endif %>
	<% if [<value>is[blank]] %>
		blank: <<value>>
	<% endif %>
	<% if [<value>!is[blank]] %>
		all values: <<value>>
	<% endif %>
</$list>
</$let>

vs. your proposed code (spacing adjusted for a clearer comparison):

Your “select” is certainly more concise!

I find I still have some conceptual questions, though:

  • Is <%case ... %> a mandatory first step, or could <%case 1 %> be replaced with <%when [match[1]] %>? For that matter, could I replace a subsequent <%when ... %> statement like <%when [match[2]] %> with <%case 2 %>?
  • I hadn’t encountered this usage of “select” before, but a quick google suggested that the select/case/when nomenclature is also used in SQL (and perhaps other languages?) I’m sure this makes it intuitive to more experienced programmers, but IMO, as a layperson, it’s not nearly as clear as if/elseif/else. And it gets especially ambiguous in a TW context, where many users will be most familiar with $select as used for dropdowns.
  • Perhaps most importantly: Could you give a more practical example of when and how you’d use this? I do appreciate the brevity of the code, but I’m struggling to come up with real-world situations in which I’d need this pattern.

Thanks for reviewing;

I would expect the select statement to be typically used with just case statements when it is a simple value such as 1, 2, 3 or red, green, blue and give it behaves like a list widget we may not use the let;

  • No inner “case or when” is mandatory
<%select [all[current]get[color]] %>
	<%case red %>
		on red
	<%case green %>
		on green
	<%case blue% >
		on blue
<%endselect%>

The term “case” here is as “in case of fire”, or “In the case where the color is red”

  • It is for when the filter evaluates to one and only one value

Similarly the %Select statement evaluates one or more values so each “when statement” is actually a subfilter testing the “value” generated in the select filter.

  • unlike the case statement it always tests the value but larger compound filters can be used
    • eg value is green and a field contains a value above some number.
    • or value is in a range?
    • or the value is a tiddler title and we want to interrogate that
  • That for more complex outcomes and conditions one would revert to the %if/Elseif structure.

On consideration of the when I think it may be best as %when-value because that indicates the test is only against the result from the select filter. And provide a %when that can be any form of condition (although it has access to the value and item variables).

Including books on learning to code, cobol and advanced basic to name a few.

I understand this as select is a html term as well, but I wonder when such standards are forceful enough, perhaps for case a separate %for %value and a generic when?

  • Select is still valid in both cases, one is to $select a value the other is to do something for a %select value/condition.

I was reluctant to go down the “for” route but perhaps it makes sense. But the current %select does what I am intuitively familiar with :thinking:

@TW_Tones I like your proposal!

Some thoughts:

  • beside %case and %when, I’d also like to see:
    • an %else clause, being rendered whenever every other clause has failed, but I guess implementation would be difficult ;
    • an %each clause, being rendered always (equivalent to <%when 1> ?)
  • regarding @etardiff’s answer about “select”:
    • some programming languages call this structure a “switch/case” instead of “select/case”, but semantically “switch” would not really fit here because in your proposal, several clauses can match - or would it?
    • what about replacing %select with %every? AFAICT “every” isn’t a html tag, and IMO it fits quite well the proposed use of a multi-valued filter.

Fred

Thanks for the clarifications!

I’d tend to agree. IMO, either %for or %every (as @tw-FRed suggested) communicates more clearly that each output of the outer filter is tested against each of the inner filter conditions.

It’d still be helpful to see some more “real-world” applications if you’ve got any in mind, though. We often hear that there’s already a lot to take in, syntax-wise, so I do worry a bit about introducing new patterns without sufficient motivation — especially if they’re visually similar to extant constructions like %if, which is explicitly not iterative. I think we’d want to demonstrate a significant advantage over simply nesting <%if ... %>...<%endif%> conditions in a $list — or, if there is no real advantage but brevity, demonstrate that this pattern would be used often enough to warrant the new syntactic sugar.

I am only reading you reply a day later, I must check the process but the intention is to permit a form of cascade with case statements where the first match is the result, otherwise no match and other cases can be handled with when.

I think else remains best only withing the existing if :thinking:

In my example the test can be any string generated from the original select filter, the when acts as if the value is given to the test eg [compare:number:ge[1]] is the same as [<value>compare:number:ge[1]]

I understand where you are coming from but the select statement can be used to test the various values from one tiddler only or many tiddlers. To me the list widget is what delivers the iteration / each structure in tiddlywiki, the if the decision and the select the case/select.

I am yet to fully explore MVV’s and I think they do offer a lot here as long as the solution remains somewhat generic, and example would be using one MVV for the list of tiddlers interrogated the other MVV for various values to test.

I agree totally, and not to do so is to continue the current problem raised in the Topic that inspired this below;

I still feel we need a rich although obvious dataset against which we can write real world examples, my first inroads to this was the periodical table data but for many its too complex. Its value was demonstrated well how @Scott_Sauyet took it ion and created Demo: Periodic Table

  • A career in ICT means examples like businesses/regions/states/Airports and other data are very familiar to me.

use case

The select or case tool is something to consider when using nested $lists or nested %if/else where the thing you are testing needs to be referred to multiple times. It helps when the structure you are building would otherwise be hard to read and complex to represent in nested %if and $list structures.

  • Typically it would only be applied on a per-tiddler basis, perhaps wrapped in a list widget that iterates the tiddler titles,
  • however there will be some cases where you want to display different outputs according to a larger set of values, the select then provides a simpler and concise representation of the logic.
    • unfortunately because select, like if , is internally restated as $list widgets it does not allow you to accumulate values :thinking: could the result be returned through a transclusion? Consider a tiddler containing a select that returns a single value depending on a set of case statements, we then transclude it for each tiddler, and obtain a value we can use to control other logic? I think this needs testing before making other decisions.

Special note @pmario

I understand there is a feature in 5.4.0 that allows the internal representation of the widget tree to be used as a return trip. That is we may be able to have a select statement to be converted to nested list widgets but have the intermediate form returned to our source script.

I think @etardiff’s example in reply to my initial confusion is a perfect example of what could be included in the documentation for conditional syntax. Her way of walking a reader through to her final suggestion is a perfect teaching methodology.

From @etardiff:

Here’s what your conditional should look like if you strip out all the {{{ }}} from the <%elseif ... %> filters as @pmario said:

<%if [<f.TLSisempty GunType>match[yes]] %>
     {{!!State}} : {{!!CityTown}} - Unknown Gun Type
<%elseif [<currentTiddler>get[GunType]has[nomenclature]] %>
     {{!!State}} : {{!!CityTown}} - {{{[<currentTiddler>get[GunType]get[nomenclature]]}}}
<%elseif [<currentTiddler>get[GunType]has[common]] %>
     {{!!State}} : {{!!CityTown}} - {{{[<currentTiddler>get[GunType]get[common]]}}}
<%elseif [<currentTiddler>get[GunType]has[abbreviated]] %>
     {{!!State}} : {{!!CityTown}} - {{{[<currentTiddler>get[GunType]get[abbreviated]]}}}
<%else%>
     {{!!State}} : {{!!CityTown}} - Unknown Gun Type
<%endif%>

However, one advantage of conditional syntax is that the result of each %if/%elseif filter is available under that filter as the variable <<condition>>. Furthermore, get returns only non-blank field values, so get[nomenclature] will automatically fail whenever has[nomenclature] is untrue.

We can take advantage of these two facts to further simplify your code:

<%if [<f.TLSisempty GunType>match[yes]] %>
     {{!!State}} : {{!!CityTown}} - Unknown Gun Type
<%elseif [<currentTiddler>get[GunType]get[nomenclature]] %>
     {{!!State}} : {{!!CityTown}} - <<condition>>
<%elseif [<currentTiddler>get[GunType]get[common]] %>
     {{!!State}} : {{!!CityTown}} - <<condition>>
<%elseif [<currentTiddler>get[GunType]get[abbreviated]] %>
     {{!!State}} : {{!!CityTown}} - <<condition>>
<%else%>
     {{!!State}} : {{!!CityTown}} - Unknown Gun Type
<%endif%>


And since this highlights some repetitive code, we could even take it a step further and store the “content” snippets as procedures, defined at the top of this tiddler:

\procedure unknown-type() {{!!State}} : {{!!CityTown}} - Unknown Gun Type
\procedure known-type() {{!!State}} : {{!!CityTown}} - <<condition>>

<%if [<f.TLSisempty GunType>match[yes]] %>
     <<unknown-type>>
<%elseif [<currentTiddler>get[GunType]get[nomenclature]] %>
     <<known-type>>
<%elseif [<currentTiddler>get[GunType]get[common]] %>
     <<known-type>>
<%elseif [<currentTiddler>get[GunType]get[abbreviated]] %>
     <<known-type>>
<%else%>
     <<unknown-type>>
<%endif%>

Finally, you can use a $let widget to reduce some of the <currentTiddler>get[GunType] redundancy:

<$let gun-type={{{ [<currentTiddler>get[GunType]] }}}>
<%if [<f.TLSisempty GunType>match[yes]] %>
     <<unknown-type>>
<%elseif [<gun-type>get[nomenclature]] %>
     <<known-type>>
<%elseif [<gun-type>get[common]] %>
     <<known-type>>
<%elseif [<gun-type>get[abbreviated]] %>
     <<known-type>>
<%else%>
     <<unknown-type>>
<%endif%>
</$let>

bobj

I can see there has been some discussion on terminology, in particular how “select” is already in use in relation to the HTML <select> element and also noting its use in SQL.

Since TW is a JavaScript application, why not use JavaScript terminology? i.e. “switch”? That is also the keyword used in C/C++ for this sort of thing.

I like this idea a lot. When the <% ... %> syntax was introduced, it was clear that <% if ... %>/<% elseif ... %>/<% else %>/<% endif %> was meant to be only the first of multiple uses of the basic idea. IIRC, a case statement was explicitly mentioned as a likely future candidate.

I’m still thinking through the details, but I agree with @andrewg_oz, not so much because of the existence of HTML’s <select> (I’d actually be more concerned about SQL), but because “select” is obscure in this space, used only in Fortran:

Primary Keyword Languages
switch C, C#, C++, Go, Java, JavaScript, PHP (classic), Swift, TypeScript
case Ada, Bash, Clojure, Common Lisp, Elixir, Erlang, Haskell, Pascal, Ruby, Scheme
match F#, OCaml, PHP 8+, Python, Rust, Scala
when Kotlin
EVALUATE COBOL
SELECT CASE Fortran
CASE SQL

While we shouldn’t let other languages and tools dictate our own practices, we should veer from them only with intention and strong reason.

My own choice here would be match.


A vaguely-related story:

A few years ago, a relatively senior manager at GigantiCorp (a different one than the current GigantiCorp where I work now, but damned similar) decided that the only solution to a fairly minor feature request in our system was to create a large domain-specific language that our business users could operate. And he would design that system himself; it would be up to us to implement it.

His language looked suspiciously like the COBOL he grew up in. A group of the more senior people on the team got together and decided to write – off hours – critiques of the design of that language to present to even more senior leadership. One of my sections was the case statement, which he insisted be called nothing but “EVALUATE” (yes, he spoke in capital letters) and below is a draft of that document (written off-hours, remember) shorn only of an introductory sentence and a concluding paragraph related to the monstrosity of a language design.

The final report seems lost to history. I really wish I had it. We did all these straightforward factual reporting, which made no editorial comments whatsoever, but which inexorably damned his approach. I miss absolutely nothing about that job, except for this group of senior developers who could pull something like this off.

We presented our findings to a senior VP. The project was scrapped, and the manager took early retirement. A co-worker fulfilled that minor feature request with three days’ work.

Case Statement Analysis (~2300 words)

Case Statements and Pattern Matching Across Programming Languages

Introduction

The “case statement” — a construct for branching on the value of an expression — is one of the oldest and most universal control-flow primitives in programming. It exists in essentially every general-purpose language, but the form it takes varies dramatically. Some languages (the C family) treat it as syntactic sugar for a computed jump, complete with fall-through behavior that has caused countless bugs. Others (Pascal, Ada, modern functional languages) treat each branch as a self-contained alternative with no implicit flow between cases. And in the last fifteen or twenty years, mainstream languages have increasingly absorbed ideas from the ML/Haskell tradition, replacing or supplementing simple value-dispatch with full pattern matching — destructuring data, binding variables, guarding on conditions, and matching by structural shape rather than just equality.

This report surveys how case-style constructs work across a representative sample of languages, with emphasis on modern usage but with a look back at COBOL, Fortran, and LISP for historical perspective.

The C Family: switch/case/default with Fall-Through

C established a template followed almost verbatim by C++, Java, JavaScript, C#, Go, PHP, and many others. The keywords are switch, case, default, and (critically) break. The defining quirk is fall-through: once execution enters a case label, it continues into subsequent cases unless explicitly halted with break (or return, throw, etc.). This is occasionally useful for grouping cases, but it is so error-prone that most modern style guides require a comment whenever fall-through is intentional, and several newer languages in the C family have abandoned it.

C’s switch is also restricted in what it can match against: integer (or integer-promoted) constants only. No strings, no ranges, no patterns. C++ inherited this limitation; Java relaxed it in Java 7 to allow strings, and modern Java (14+) introduced switch expressions with arrow syntax (->) that don’t fall through, plus pattern matching for types and records.

Go kept the keywords but reversed the default: cases do not fall through, and you must write fallthrough explicitly to opt in. Go also allows expressions in case labels, multiple values per case, and a “type switch” form (switch v := x.(type)) for runtime type discrimination.

C# historically required a break (or goto case) at the end of every non-empty case — fall-through is a compile error rather than a default. Recent versions added rich pattern matching: type patterns, property patterns, relational patterns, and switch expressions using =>.

JavaScript and PHP retain classic C-style fall-through. JavaScript’s switch uses strict equality (===).

Pascal, Ada, and the “No Fall-Through” Tradition

Pascal introduced case ... of ... end with no fall-through and explicit support for ranges (1..5:) and lists of values per branch. Ada continued and tightened this: every possible value of the discriminant must be covered (the compiler enforces exhaustiveness), and others serves as the default. There is simply no way to fall through; each when branch is a single statement.

This tradition — exhaustiveness checking, no fall-through, range and list support — turns out to anticipate much of what modern functional languages do.

Shell, SQL, and Other Niches

Bash uses case ... in ... esac with glob-style patterns separated by | and terminated by ;;. Variants ;& (fall through unconditionally) and ;;& (continue testing remaining patterns) were added later. The patterns are real shell patterns — *.txt, [0-9]* — making bash’s case unusually expressive for string dispatch.

SQL has two forms: a simple CASE expr WHEN val THEN ... ELSE ... END and a searched form CASE WHEN condition THEN ... ELSE ... END. It is an expression, not a statement, and is one of the few places SQL offers conditional logic in a portable way.

Old Languages: COBOL, Fortran, LISP

COBOL uses EVALUATE, introduced in COBOL-85. It is surprisingly powerful: you can evaluate multiple subjects at once, and each WHEN clause can list values, ranges, or TRUE/FALSE for arbitrary boolean conditions. There is no fall-through. WHEN OTHER is the default.

Fortran added SELECT CASE in Fortran 90. Each CASE lists values or ranges (CASE (1:10)); there is no fall-through; CASE DEFAULT handles the fallback. Earlier Fortran had only IF/GOTO and the now-deprecated computed/arithmetic GOTO, which was a primitive ancestor of switch.

LISP and its descendants take a different approach because everything is an expression. Common Lisp has case (and ecase/ccase for exhaustiveness), cond (a chain of test/result pairs), and typecase (dispatch on type). Scheme has case and cond. Clojure has case (constant-time dispatch on compile-time literals), cond, condp, and — in the spirit of modern times — core.match for full pattern matching. None of these involve fall-through; each branch is a self-contained expression that produces a value.

Modern Pattern Matching: ML, Haskell, Rust, Scala, Swift, Python, Elixir

The functional tradition reframes “case” entirely. Instead of dispatching on a single value, you match against the shape of data. Patterns can:

  • bind variables (Some(x) matches and binds x),
  • destructure tuples, lists, records, and algebraic data types,
  • carry guards (when x > 0 / if x > 0),
  • nest arbitrarily deeply,
  • be checked for exhaustiveness by the compiler.

Haskell uses case ... of, with each branch a pattern followed by -> and an expression. Pattern matching also works directly in function definitions via multiple equations.

OCaml/F# use match ... with and |-separated patterns leading to ->.

Rust has match, with => between pattern and arm, mandatory exhaustiveness, range patterns (1..=5), guards (if cond), | for or-patterns, and _ as the wildcard. Rust also has if let and while let for single-pattern shorthand, and (stabilized 2024) let ... else for the early-return idiom.

Scala has match with case keywords; patterns include extractors via unapply, type patterns, and guards.

Swift uses switch with case keywords but in pattern-matching style — no fall-through by default (use fallthrough to opt in), exhaustiveness required, and full destructuring/binding/where-guard support. Swift’s switch is the unusual case of borrowing C-family keywords while delivering ML-family semantics.

Python added structural pattern matching in 3.10 with match/case. Patterns include literals, classes (with positional or keyword sub-patterns), sequences ([1, 2, *rest]), mappings, captures, | for or-patterns, as for binding, and if for guards. The wildcard is _. There is no fall-through.

Elixir (and Erlang) lean heavily on pattern matching: case, multi-clause function heads, and the = operator itself is a pattern match. Guards use when.

Kotlin uses when, which doubles as both switch-replacement and a more general conditional (no subject expression needed). It can match values, ranges (in 1..10), types (is String), and is exhaustive when used as an expression on a sealed type or enum.

Ruby has case ... when ... end, where when uses the case-equality operator === — letting you match ranges, classes, regexes, and lambdas. Ruby 3.0 added case/in for full structural pattern matching, alongside the older case/when.

Summary Table

Language Keywords Syntax Snippet Notes
C switch, case, default, break switch (x) { case 1: ...; break; default: ...; } Integer constants only. Fall-through by default.
C++ switch, case, default, break, [[fallthrough]] switch (x) { case 1: [[fallthrough]]; case 2: ...; break; } Like C; C++17 added [[fallthrough]] attribute to silence warnings.
Java switch, case, default, break, yield, -> switch (x) { case 1, 2 -> "small"; default -> "big"; } Modern arrow form: no fall-through, expression-valued, pattern matching on types/records (Java 21+).
C# switch, case, default, break, when, => var s = x switch { 1 => "one", > 10 => "big", _ => "other" }; No implicit fall-through (compile error). Switch expressions, type/property/relational patterns, when guards.
Go switch, case, default, fallthrough switch x { case 1, 2: ...; case 3: fallthrough; default: ... } No fall-through by default; fallthrough is opt-in. Type switch: switch v := x.(type). Cases can be expressions.
JavaScript switch, case, default, break switch (x) { case 1: ...; break; default: ...; } Fall-through by default. Strict equality (===).
TypeScript same as JS same as JS, plus exhaustiveness via never No native pattern matching; idiom is to assign to a never-typed variable in default for exhaustiveness checking. Proposal in flight.
PHP switch, case, default, break, match match($x) { 1, 2 => 'small', default => 'big' } Classic switch has fall-through and uses ==. PHP 8 added match expression: strict ===, no fall-through, exhaustive, returns a value.
Pascal case, of, else, end case x of 1: ...; 2..5: ...; else ... end; No fall-through. Ranges supported.
Ada case, is, when, others case X is when 1 | 2 => ...; when 3..5 => ...; when others => ...; end case; Exhaustiveness enforced by compiler. No fall-through. Ranges and | lists.
Bash case, in, esac, ;;, ;&, ;;& case $x in foo|bar) ...;; *.txt) ...;; *) ...;; esac Glob patterns. ;& falls through; ;;& continues testing.
SQL CASE, WHEN, THEN, ELSE, END CASE x WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END Expression, not statement. Simple and searched forms.
COBOL EVALUATE, WHEN, WHEN OTHER, END-EVALUATE EVALUATE X WHEN 1 ... WHEN 2 THRU 5 ... WHEN OTHER ... END-EVALUATE Multi-subject evaluation. Ranges via THRU. WHEN TRUE/WHEN FALSE for boolean conditions. No fall-through.
Fortran SELECT CASE, CASE, CASE DEFAULT, END SELECT SELECT CASE (X); CASE (1); ...; CASE (2:5); ...; CASE DEFAULT; ...; END SELECT Ranges via (low:high). No fall-through.
Common Lisp case, ecase, ccase, cond, typecase (case x (1 'one) ((2 3) 'small) (otherwise 'other)) Expression. ecase errors if no match (exhaustive). cond for arbitrary tests; typecase for type dispatch.
Scheme case, cond, else (case x ((1) 'one) ((2 3) 'small) (else 'other)) Like Common Lisp. Uses eqv? for comparison.
Clojure case, cond, condp, core.match/match (case x 1 "one", (2 3) "small", "other") case is constant-time, compile-time literals only. core.match adds full pattern matching.
Haskell case, of, ->, | (guards) case x of Just n -> n; Nothing -> 0 Full pattern matching with guards. Also via function-equation patterns. Exhaustiveness warned.
OCaml match, with, |, ->, when match x with | Some n when n > 0 -> n | _ -> 0 Patterns destructure variants, tuples, records, lists. Exhaustiveness checked.
F# match, with, |, ->, when match x with | Some n when n > 0 -> n | _ -> 0 Like OCaml. Active patterns add user-defined matchers.
Rust match, =>, _, if (guards), |, ..=, if let, while let, let ... else match x { 1..=5 => "small", n if n > 10 => "big", _ => "other" } Mandatory exhaustiveness. Range, or, binding, struct/enum/tuple patterns. if let for single-pattern shorthand.
Scala match, case, =>, _, if (guards) x match { case Some(n) if n > 0 => n; case _ => 0 } Extractor objects via unapply. Type patterns. Used heavily for ADTs.
Swift switch, case, default, where, fallthrough switch x { case 1...5: ...; case let n where n > 10: ...; default: ... } No fall-through unless fallthrough. Exhaustive. Pattern matching with binding, ranges, tuples, type casts via as, where guards.
Python match, case, _, |, as, if (guards) match p: case Point(x=0, y=0): ...; case Point(x, y) if x == y: ...; case _: ... Structural pattern matching since 3.10. Class, sequence, mapping, capture patterns. No fall-through.
Kotlin when, is, in, else, -> when (x) { 1, 2 -> "small"; in 3..10 -> "mid"; is String -> "str"; else -> "other" } Doubles as general conditional (no subject needed). Exhaustive on sealed/enum when used as expression.
Ruby case, when, in, then, else, end case x; when 1..5 then "small"; when String then "str"; else "other"; end when uses === (matches ranges, classes, regexes). Ruby 3.0+ adds case/in for full pattern matching with destructuring.
Elixir case, do, end, ->, when case x do {:ok, n} when n > 0 -> n; _ -> 0 end Pattern matching pervasive. Multi-clause function heads are the more idiomatic dispatch. Guards via when.
Erlang case, of, end, ;, ->, when case X of {ok, N} when N > 0 -> N; _ -> 0 end. Same family as Elixir. Guards restricted to a small set of pure functions.

Observations

A few patterns emerge from this survey. First, fall-through is a historical accident of C that is steadily being designed away — Go made it opt-in, C# made it an error, and the entire ML/Haskell/Rust lineage rejects the very idea. Second, modern languages converge on three features: exhaustiveness checking, no fall-through, and structural pattern matching. Even languages that started out with simple C-style switches (Java, Python, Ruby, JavaScript via proposal) are growing toward this model. Third, the boundary between “case statement” and “pattern match” has effectively dissolved in current-generation languages: what Rust calls match and what Python calls match are the same construct, and they subsume what older languages split across switch, if-chains, and type tests. The case statement is no longer about jumping to a label; it is about decomposing data.

The conditional shortcuts have been introduced as a shortcut for nested <$list widgets.

<% if %>
<% elseif %>
<% else %>
<% endif %>

A single list widget with an emptyMessabe can easily be used as an if-statement. But if we need an else if, it starts to get messy.

It needs nested list-widgets with several emptyMessages, where the emptyMessages may contain a lot of code, that is almost impossible to debug.

So it makes perfect sense to create a shortcut syntax that makes the logic readable in simple.

The same goal has to be valid for a new requested <% select %> shortcut.

But as @etardiff’s code shows, what is requested can easily be recreated using a list widget with non-nested IF’s.

This construction is even more powerful than the requested “select”, since it has the possibility to:

  • use several IF states as in the example
  • use IF ELSEIF, if only one state should be active.
  • or combine IF - ELSEIF with a IF (is[blank] or !is[blank]) … to catch default state if needed.

So it’s super flexible in a simple to read way.

<$let valueList="1 2">
<$list filter="[<valueList>split[ ]]" variable="value">
	<% if [<value>match[1]] %>
		when 1: <<value>>
	<% endif %>

	<% if [<value>match[2]] %>
		when 2: <<value>>
	<% endif %>

	<% if [<value>is[blank]] %>
		blank: <<value>>
	<% endif %>

	<% if [<value>!is[blank]] %>

		every values: <<value>>
	<% endif %>
</$list>
</$let>

My personal conclusion

From my point of view the suggested select only adds complexity, for no real win, because what we have is more powerful already.

What I could see on the other hand is: Get rid of chained <$reveal widgets with a state machine. But that’s a different thread.

Thanks for all the view points folks.

On reflection I realised the “select statement” came from my education in Australia and the international coding books I read, especially starting to write non trivial solution’s. An important part of this is recognising that a particular problem follows a select or case structure, when starting to design a solution, in pseudocode even.

  • For reference I learned “hierarchical programming”, and for sometimes very complex business solutions, where the code also needed to be read by peers, auditors, future program maintanece etc…

Personally I am not wedded to a particular term and this depends on the scope and intention of the final structure(s)

  • If we look at @Bob_Jansen’s original code example we can see it can be converted to the simple case structure. That is the nature of his design requirement.

The fact that the if and else if statement is a structure that provides a somewhat universal solution is not in question here, placing multiple inner if statements, nesting or structuring them allows very sophisticated and structured solutions.

The reason I suggested this is to provide the “meta components” of coding language to simplify, not make it more complex and your example arguably only survives to be somewhat readable because there is not much complexity between each case. If there was more diversity and complexity in “each case” the code quickly becomes saturated with end-if clauses, and wholly independent filters that must be both tested every time and debuged against every other filter. This is not overly performant either, as even once an answer, if found, it will still check every other condition.

Consider the simplest case I gave;

  • In this example select could become %if-value and %case become %on-value statements.

If each case had a diverse complex contents it would remain easier to read and write and the filters can be made to remain optimal and automatic, thus removing many opportunities to introduce errors.

  • For a case statement of the above we can ensure they are placed in the most common order and as soon as one is a match it displays the content and exits or moves on to the next item. The above nested elseif structure of @pmario

Beyond the case statement

In my initial post I introduced the when clause so I could introduce more complex comparisons, a catch all or no match value etc… This was simply to foreshadow how sophisticated it could get.

However I think we can take inspiration from the if structure and identify common code structures used in tiddlywiki to find a few new code structures that can help enhance the quality, reducing errors, providing best practice, reduce verbosity etc..

  • Such may also help programmers and novices alike start using tiddlywiki more easily

Widget vs function

In reality the above suggestion, like the current %if, these structures are parsed into list widgets, and as I have stated previously in talk.tiddlywiki widgets are where content goes to be displayed, even if nested. We tend not to wikify a widget before the final render, ie widgets don’t return their output to the TiddlyWiki script.

However functions are evaluated before the final render so if we want a case statement such that based on various rules returns something like a “discount rate”, we need to use functions/filters/custom operators instead.

  • I would like to illustrate the function equivalent along side a documented case statement as well.
  • Also research the transclusion method to return the output to “evaluate the full statement”.

I get the appeal of a select/case thing, but I’d be careful adding a new control structure unless the current widgets really can’t express it cleanly. Filtered branches or nested conditions can be uglier, but they stay pretty explicit and fit the way TW already works.

Welcome to the community @HenryTiddlywiNotes thanks for your viewpoint.

I understand where you are coming from, and in general agree, they key being when it “can be expressed cleanly”. Although it is clear to me some coding problems, or lets say logical or structured problems, become verbose and contain within them their own complexity.

The idea of a case statement does I believe demonstrate it.

The case statement can be used to provide a single filter which is then expanded (internally) into multiple list widgets each with their own filter and in an optimised structure for the case/select/switch statement. In fact the simplest form is “case value” - “do this”. The need to ensure each and every filter so generated is “automated away” and no longer demands the author to ensure consistency, or ensure that it is nested correctly, or unnecessary parameters given to the list widget etc… the better.

  • Another example is simplifying code we need to use a lot, even if its not too complex or is not too fragile.

This difference does become more obvious to the different skill levels of the user, as a active communicator/tiddlywiki “teacher” if a problem can be characterised as a “case statement” it is actually easier for me to direct someone how to achieve something. If instead I expanded it into nested if elseif statements, or even harder nested list widgets the explanations get much more complex, and more fragile (has many more opportunities to break it) especially when communicating with novices, in TiddlyWiki or complex coding structures.

Of course the value is realised more if the coding structure we provide is commonly used, or commonly problematic for users. Arguably this is why the %if statement is so popular, because it does exactly that, in addition to the fact it resembles other programming languages and plain language.

  • but of course it is hard to understand which structures are harder to learn.
  • my guess is until people are well versed in “filter coding”, which is both tiddlywikis greatest strength and weakness if we can give solutions that demand less filter and list widget knowledge, I expect we are providing something useful.

By asking the question at the top of this topic I am endeavouring to see if we can identify and extend TiddlyWiki’s available structures and expect we may find at least a few that improve, simplify and make more robust, peoples use of TiddlyWiki script.

Perhaps share what you found hardest to implement in TiddlyWiki so far, so we can explore if there exists useful coding structures or even other solutions.