如果if只是一个函数
When if is just a function

原始链接: https://ryelang.org/blog/posts/if-as-function-blogpost-working-on-it_ver1/

## 重新思考控制流:当 `if` 变成函数 传统的编程语言,如 Python,将控制结构(`if`、`for`、`while`)视为特殊的内置语法。而 Rye、REBOL 和 Red 等语言采取了不同的方法:它们将这些结构实现为*普通函数*。这种看似微小的改变释放了显著的优势。 **一致性、灵活性和可扩展性**是关键。将控制流视为函数允许进行组合,将它们作为参数传递,甚至创建针对特定需求定制的版本——这是 Python 严格语法无法做到的。Rye 不用记住大量的关键字,而是利用单一的通用模式:带有参数的函数调用,包括代码块。 这种方法简化了代码,并实现了强大的技术,例如将数据直接**管道**传输到控制流函数,以及**偏应用**来创建自定义控制结构。虽然由于运行时函数调用可能存在潜在的**性能**权衡,但统一的方法可以简化整体**工具**和优化。 最终,将控制流变成函数模糊了语言特性和库函数之间的界限,提供了一种更动态和富有表现力的编程体验。

一个 Hacker News 的讨论围绕着在 Lisp 方言 Janet 中,将条件逻辑(通常由 `if` 或 `cond` 处理)重新实现为纯函数。用户 “soheilpro” 分享了一个名为 `better-cond` 的函数,它使用组合编程技术实现了这一点。 传统的 Lisp 和 Clojure 的 `cond` 需要特定的测试-结果对格式。`better-cond` 消除了这种需求,作为一个标准的函数调用,可以被映射或与其他函数组合。这允许更灵活和函数式的控制流。 讨论涉及 Lisp 的求值模型与 Rebol 等语言的不同,这影响了 `if` 是否可以成为一个函数。另一位评论员指出,在这些实现中考虑求值顺序(应用式 vs. 正常式)的重要性。代码示例展示了 `better-cond` 处理多个变量并产生复杂函数输出的能力。
相关文章

原文

HELLO

In Python, when you write if x > 5: print("big"), you’re using special syntax baked into the language. You can’t change how if works. You can’t compose it, pipe it, or partially apply it. You can’t pass if as an argument to another function.

But what if you could? What if if, for, while and even fn and var were just regular functions?

In languages like REBOL, Red and Rye they are.

Three Reasons This Matters

Consistency. Most languages treat control structures as special forms—exceptions to the normal rules. In languages like Rye and REBOL, they’re ordinary functions that follow the same patterns as everything else.

Flexibility. Functions can be composed, passed around, and combined. When control structures are functions, you inherit all those capabilities.

Extensibility. If if and for are just functions, you can create your own specialized versions for specific purposes. No part of the language remains off-limits.

Let’s see what this looks like in practice.

Looking at If

Here’s a conditional in Python:

temperature = 36

# We can evaluate and print an expression
print(temperature > 30)
# prints: True

# But we can't print a block of code without executing it
# (well, we could stringify it, but that's not the same)

# Standard conditional - special syntax
if temperature > 30:
    print("It's hot!")
# prints: It's hot!

Now compare this to Rye:

temperature: 36

; Evaluates expression and prints the result
print temperature > 30
; prints: true

; In Rye, blocks are data - we can print them
print { print "It's hot!" }
; prints: print "It's hot!"

; The 'do' function evaluates a block
do { print "It's hot!" }
; prints: It's hot!

; And here's the conditional - also just a function
if temperature > 30 {
    print "It's hot!"
}
; prints: It's hot!

Look at that last if statement. It’s not special syntax it’s a function call. While functions print and do take one argument, if function takes two arguments:

  1. A condition that evaluates to true or false
  2. A block of code to run if the condition is true

You might wonder: “Won’t the block execute immediately when passed as an argument?” Here’s the key insight: in Rye, code blocks { ... } are values. They don’t evaluate until you explicitly tell them to. The if function receives the block as data and decides whether to evaluate it based on the condition.

When code is data, control flow doesn’t need to be special.

Single pattern

In Python, every language feature has its own syntax:

# Conditionals - keywords and colons
if x > 5: 
    y = "big"

# Loops - different syntax
for i in range(10): 
    print(i)

# Iteration - similar but distinct
for item in ["milk", "bread", "pickles"]: 
    print(item)

# Functions - def keyword, parentheses, colons, indentation
def add(a, b): 
    return a + b

In Rye, one pattern applies everywhere:

; Conditionals - function taking a boolean and a block
if x > 5 { y: "big" }

; Counting loop - function taking an integer and a block
loop 10 { .print }

; Iteration - function taking a collection and a block
for { "milk" "bread" "pickles" } { .print }

; Functions - function taking argument list and body block
add: fn { a b } { a + b }

Every construct follows the same shape: a name, followed by arguments, some of which happen to be blocks of code. Instead of memorizing Python’s 35+ keywords and their unique syntaxes, you learn one universal pattern.

There’s no longer a meaningful distinction between “language features” and “library functions.”

Consistency & Flexibility

In Python, if and for are statements, not values. But in Rye they are function. and first of all you can compose functions.

loop either temperature > 32 { 3 } { 1 } { prns "Hot!" }
; Prints: Hot! Hot! Hot!

; We can use optional parenthesis to see the evaluation order (similar to lisp-s)
( loop ( either ( temperature > 32 ) { 3 } { 1 } ) { prns "Hot!" } )
; Prints: Hot! Hot! Hot!

prns prints a value with a space (no newline)

In Python using if statement, this would take multiple lines and a variable mutation:

repeats = 1
if temperature > 32:
    repeats = 3

for _ in range(repeats):
    print("Hot!", end='')
# Prints: Hot! Hot! Hot! 

Luckily, python has another special syntax where if keyword becomes an expression:

for _ in range(3 if temperature > 31 else 1):
    print("Hot!", end='')
# Prints: Hot! Hot! Hot! 

While special syntax is usualy cemented in place, there are multiple ways to provide arguments to function calls.

hot-code: { print "Hot!" }
is-hot: temperature > 30

if is-hot hot-code
; prints Hot!

loop 2 hot-code
; Prints: Hot!
;         Hot!

Piping Into Control Flow

Functions in Rye can accept a first (or second) argument from the left, so the same applies to “flow control”-like functions of course. Read more about this in Meet Rye.

; Pipe a condition into if
temperature > 30 |if { print "Hot!" }
; Prints: Hot!

3 .loop { .prns }
; Prints: 0 1 2

; Pipe a collection into for
{ "Hot" "Pockets" } |for { .print }
; Prints: Hot
;         Pockets

In Python, you can’t pipe into if or for because they’re not values, they’re syntax.

Applying functions

In Rye we can apply functions to it’s arguments.

apply ?concat { "Bob" "Odendirk" }

woof: { print "woof" }
meov: { print "meov" }

animals: [ [ false woof ] [ true meov ] ]

for animals { .apply* ?if }
; Prints: meov

?word - is a get-word, it doesn’t evaluate a function bound to the word but returns it word* - star at the end of the word causes function to take second argument from the left, not the first one

Partial Application

In Rye we can partially apply functions.

add-five: partial ?_+ [ _ 5 ]

add-five 10
; returns 15

three-times: partial ?loop [ 3 _ ]

; Use it like any other function
three-times { prns "Hey!" }
; Hey! Hey! Hey!

You’ve created a custom control structure by partially applying a built-in one. Try doing that with Python’s for loop.

Higher-Order Control Flow

We can pass function as arguments to other functions

verbosify\2: fn { fnc } {
    closure { a b } {
	probe a 
        probe b
        fnc a b
    }
}

myconcat: verbosify\2 ?concat
myconcat "AAA" "BBB"
; Prints:
;  [String: AAA]
;  [String: BBB]

myif: verbosify\2 ?if
myif temperature < 30 { print "cold!" }
; Prints:
;  [Boolean: false]
;  [Block: ^[Word: print] [String: cold!] ]

Extensibility

Since control flow is just functions, you can write your own control structures indistinguishable from built-ins.

Unless and Until

; Want the opposite of if?
unless: fn { condition block } {
    if not condition block
}

unless tired { 
    print "Keep working!" 
}

; Need an until loop?
until: fn { condition block } {
    loop {
        r:: do block
        if do condition { return r }
    }
}

count:: 0
until { count > 5 } {
    print count
    count:: inc count
}

Unlimited control flow functions

When you accept this you see that there is no hard border of hard limit to what control strucutre like function you should have. You load them on library level and specific libraries can provide new ones or offer you functionality in shape that would usually be reserved to special forms.

Part of the base functions, but could be considered special


switch password {
	"sesame" { "Opening ..." }
	"123456" { "Self destructing!" }
}

; I'l admit, I added cases control-function just for the fizz-buzz 
; because I could :P

for range 1 100 { :n
	cases " " {
		{ n .multiple-of 3 } { "Fizz" }
		{ n .multiple-of 5 } { + "Buzz" }
		_ { n }
	} |prns
}

; outputs: 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 ...

These are usually higher order functions (functions accepting functions) but in Rye they are more similar to for-loop.


{ "anne" "bob" "alan" } 
|filter { .start-with "a" }
|map { uppercase } :names
|for { .print }

names .reduce 'acc { .concat acc }

Even external libraries can have their own functions that utilize blocks of code directly.

For example OpenAI library has a Chat\stream function that accepts a block of code into which it injects a part of string for each stream event, similar to for loop.

openai Read %.token
|Chat\stream "A joke of the day?" { .prn }

prn - prints a string without adding a newline or space at the end

The Trade-offs

Performance

Python can optimize special forms at compile time. In Rye, if is a function call with runtime overhead.

Tooling

IDEs know what Python’s if, for, def are and can provide specialized support. When everything is a function, tools have less to grab onto. On the other hand everything is of the same type, so it si much simpler to make tools for a language like that.

But …

With langauge like Python you have to optimize and provide tools for every special syntax and construct separately, while in Rye, you “just” have to make function calls as fast as possible, there is just one construct to optimize and provide tooling around!


Interested in learning more? Check out Rye, REBOL or Red to see these ideas in action.

联系我们 contact @ memedata.com