Skip to content(if available)orjump to list(if available)

When if is just a function

When if is just a function

49 comments

·October 14, 2025

veqq

Combinatory programing offers functional control flow. (Here is a straight forward explanation: https://blog.zdsmith.com/series/combinatory-programming.html ) I was inspired to write `better-cond` in Janet:

    (defn better-cond
    [& pairs]
    (fn [& arg]
        (label result
            (defn argy [f] (if (> (length arg) 0) (f ;arg) (f arg))) # naming is hard
            (each [pred body] (partition 2 pairs)
                (when (argy pred)
                (return result (if (function? body)
                                    (argy body) # calls body on args
                                    body)))))))
    Most Lisps have `cond` like this:

    (def x 5)
    (cond
    ((odd? x) "odd") ; note wrapping around each test-result pair
    ((even? x) "even"))

    Clojure (and children Fennel and Janet) don't require wrapping the pairs:

    (def x 5)
    (cond
    (odd? x) "odd"
    (even? x) "even")

    My combinatoresque `better-cond` doesn't require a variable at all and is simply a function call which you can `map` over etc.:

    ((better-cond
    (fn [x] (> 3 x)) "not a number" # just showing that it can accept other structures
    odd?   "odd"
    even?  "even") 5)

    Of course, it can work over multiple variables too and have cool function output:

    (defn recombine # 3 train in APL or ϕ combinator
    [f g h]
    (fn (& x) (f (g ;x) (h ;x))))

    (((better-cond
    |(function? (constant ;$&))
    |($ array + -)) recombine) 1 2) # |( ) is Janet's short function syntax with $ as vars

roxolotl

Do you have any recommendations for a language where you _have to_ use these concepts. I love playing with them but I find that unless i’m paying a lot of attention in most cases I fall back to a less functional style even in a language like Janet. I’d love to find a language where you largely have to use these combinatorial logic style functions so I can’t just default back to other styles.

veqq

J and BQN (APL has off-ramps...)

https://code.jsoftware.com/wiki/Essays/Tacit_Expressions

https://mlochbaum.github.io/BQN/doc/tacit.html and https://mlochbaum.github.io/BQN/doc/control.html

Forth, Factor and Uiua (which combines the above approach) don't use these concepts yet are also inherently point-free, and without lambdas so you definitely wouldn't be able to rely on functional techniques!

ivanjermakov

The drawback is that this approach elevates code blocks to first class. It means that there is a semantical difference between a value that is a block and a value that is a result of a block. This reduces code clarity, because now block def/result is discriminated by context instead of syntax.

- closures get tricky, i.e. having outer scoped variables within a block

- inter-block operators still need special care, e.g. return should return from a function or a nearest block, same for break/continue/etc.

hatthew

This is interesting, but I'm not convinced it's better than the python it's being compared to. Memorizing and understanding the behavior of functions that perform control flow seems no easier than memorizing and understanding hardcoded syntax/keywords. The additional flexibility of making everything a first-class citizen allows people to write code that is too clever for its own good. I could be wrong but I think there is a broad consensus that reflection is a Bad Idea.

Open to being convinced otherwise

(tangent but related, aren't the "Loops" and "Iteration" examples given for python literally the exact same syntax, with the exception of changing how the iterable is generated?)

Nevermark

> I could be wrong but I think there is a broad consensus that reflection is a Bad Idea.

Reflection may be bad in practice for other reasons/conditions, but the lack of simple/minimal/regular primitive conventions in many languages, makes reflection a basket of baddies.

The code blocks of Rye seem comparable to closures, which is a sensible thing to have. Once all code blocks are closures, there are fewer concepts to wrangle, and functional control makes excellent sense.

hatthew

That makes sense, thanks!

hshdhdhehd

I agree. In any somewhat functional language (I.e. all the mainstream ones) you can wrap "if" in a function if you please.

E.g.

    function funif (b, f) {
       return (b && f())
    }

If you want to do clever stuff. I never feel the need as I would rather abstract over bigger things.

solomonb

Given an algebraic data type such as:

    data List a = Nil | Cons a (List a)
You can define its recursion principle by building a higher-order function that receives an element of your type and, for each constructor, receives a function that takes all the parameters of that constructor (with any recursive parameters replaced by `r`) and returns `r`.

For `List` this becomes:

   foldr :: (() -> r) -> (a -> r -> r) -> List a -> r
The eliminator for `Nil` can be simplified to `r` as `() -> r` is isomorphic to `r`:

   foldr :: r -> (a -> r -> r) -> List a -> r
   foldr z f Nil = z
   foldr z f (List a xs) = f a (foldr f z xs)
For `Bool`:

    data Bool = True | False
We get:

    bool :: a -> a -> Bool -> a
    bool p q True = q
    bool p q False = p
Which is precisely an If statement as a function!

:D

ozy

Useless unless the logical operators receive their rhs unevaluated. And that is generalized as a language feature.

sparkie

A general language feature would be fexprs, or call-by-name (which can be combined with call-by-value using call-by-push-value).

In Kernel[1] for example, where operatives are an improved fexpr.

    ($define! $if
        ($vau (condition if-true if-false) env
            ($cond 
                ((eval condition env) (eval if-true env))
                (#t (eval if-false env)))))
$vau is similar to $lambda, except it doesn't implicitly evaluate its operands, and it implicitly receives it's caller's dynamic environment as a first class value which gets bound to env.

$lambda is not actually a builtin in Kernel, but wrap is, which constructs a function by wrapping an operative.

    ($define! $lambda
        ($vau (args . body) env
            (wrap (eval (list* $vau args #ignore body) env))))
[1]:https://ftp.cs.wpi.edu/pub/techreports/pdf/05-07.pdf

analog31

Interestingly, Excel provides "if" as a function:

=if(condition, value-if-true, value-if-false)

middayc

Yes, Io language is closest to that syntax probably:

https://iolanguage.org/guide/guide.html#Introduction

analog31

I wonder if it's more readable to non-programmers than control flow.

jrochkind1

Smalltalk, anyone? I guess the OO version.

sebastianconcpt

Yeah, here. They should know the feeling of booleans as instances and ifTrue: ifFalse: as methods. But for us is such an obvious thing that isn't really something too remarkable. It normalized language awesomeness.

middayc

There is a language https://sprylang.se/index.html which started more as Rebol and moved towards Smalltalk.

spankalee

I wish they showed the `else` syntax, because the traditional ALGOL-style if-then-else statement doesn't look native when shoved into most function call notations, unless you have something pretty interesting around named parameters and expressions delimiters.

iamevn

See the `either` function further down

  either some-condition { print "was true" } { print "was false" }

middayc

there is no if { } else { } in REBOL or Rye and it wouldn't really fit. There is either function that accepts two code blocks. It can act as a typical if / else or as a ternary expression as it also returns the result of a block:

    print either pwd = "correct" { "Hello" } { "Locked" }
This is Rebol's doc on either, Rye's works exactly the same: https://www.rebol.com/docs/words/weither.html

null

[deleted]

icepat

Is this just a quirk in my display, or are all the code blocks in this formatted like a CIA black highlighter

singlow

It's only a problem if I have my browser set to use dark theme or system theme and my system theme is dark if I switch it to light theme. Everything looks good. So most likely he's using some kind of CSS framework that's automatically responding to the dark theme, but other styles that he's hand coded are not compatible with it

middayc

I checked in Firefox and Chrome (on Linux) and code samples look OK to me. What browser/OS are you using. Maybe send me a screenshot at janko dot itm at gmail.

blauditore

Same here. I guess it's an issue with (system) dark theme (you can simulate that in dev tools. Android here, so must be Chrome.

middayc

I fixed it thanks! I was able to activate dark more in Firefox on desktop and find problems with CSS.

middayc

Thank you all for heads up! I was playing with CSS and didn't test the dark mode. I think it's fixed now.

null

[deleted]

null

[deleted]

davidw

Seems a bit like Tcl, which lets you create your own control structures like that.

middayc

I don't know a lot about Tcl, but one thing I know is said for it "everything is a string". In REBOL's it's somewaht reverse as all this live code are REBOL (Rye) values and REBOL (and Rye) have an unusual number of datatypes, REBOL 30+ (many literal types), which it uses as additional information for functions to work with, and is usefull at creating dialects

For example file-path, url and email address are distinct types in REBOL where in mosta languages are just strings.

pwg

Or redefine the language provided 'if' statement, in the case that one wanted to do so.

middayc

You can't really redefine if because everything is a constant, but you can define if in your own context yes.