[HN Gopher] When if is just a function
       ___________________________________________________________________
        
       When if is just a function
        
       Author : soheilpro
       Score  : 97 points
       Date   : 2025-10-14 14:20 UTC (4 days ago)
        
 (HTM) web link (ryelang.org)
 (TXT) w3m dump (ryelang.org)
        
       | blahgeek wrote:
       | Seems lispy
        
         | taeric wrote:
         | Without the discussion of applicative-order versus normal-
         | order.
        
         | middayc wrote:
         | BTW: In Lisps, _if_ is still a special form  / macro, because
         | in Lisp lists are evaluated by default, in Rebols _if_ can be a
         | function because blocks (lists) aren 't evaluated by default.
        
           | kazinator wrote:
           | You can rarely successfully generalize about languages in the
           | Lisp family. :)
           | 
           | TXR Lisp: (relevant to this article) there is an _iff_
           | function that takes functional arguments.
           | 
           | Square the odd values in 0 to 9:                 1> (mapcar
           | [iff oddp square use] 0..10)       (0 1 2 9 4 25 6 49 8 81)
           | 
           | The _use_ function is a synonym of _identity_ : i.e. just use
           | the incoming value as-is
           | 
           | Square the even ones instead by inverting oddp with notf:
           | 2> (mapcar [iff [notf oddp] square use] 0..10)       (0 1 4 3
           | 16 5 36 7 64 9)
           | 
           | Get rid of _use_ with _iffi_ : a two-argument _iff_ with an
           | implicit identity else:                 3> (mapcar [iffi oddp
           | square] 0..10)       (0 1 2 9 4 25 6 49 8 81)
           | 
           | Now about the point about Lisps and if: the regular _if_
           | operator with value and expression arguments has a companion
           | _if_ function:                 4> (special-operator-p 'if)
           | t       5> (fboundp 'if)       t
           | 
           | Unlike in some other dialects like Common Lisp, a symbol can
           | have a binding in the macro or operator space, _and_ in the
           | function space at the same time.
           | 
           | But this _if_ is not useful control. It 's useful for things
           | like being able to map over a function-like facsimile of the
           | _if_ operator. E.g. take an element of the (a b c d) or (x y
           | z w) list depending on whether the leftmost list has a nil or
           | t:                 6> [mapcar if '(nil t nil t) '(a b c d)
           | '(x y z w)]       (x b z d)
           | 
           | In the reverse direction, being able to write a macro for a
           | function function exists, allows for ordinary macros to be
           | "compiler macros" in the Common Lisp sense: provide
           | optimizations for certain invocations of a function.
           | 
           | This dialect is not even "weird"; overall it is Common-Lisp-
           | like. Right down to the multiple namespaces, which is why the
           | [...] syntax exists for referring to functions and variables
           | in a combined virtual namespace.
        
             | veqq wrote:
             | What's the benefit of being implemented as lisp-2 but
             | acting like lisp-1 with [ ]? Why not just be a lisp-1?
             | 
             | > TXR is an original notation for matching entire text
             | documents or streams, inspired by the unification that
             | underlies logic programming systems
             | 
             | This has me hooked.
        
               | kazinator wrote:
               | while there are certain simplicities and economies in a
               | Lisp-1, it's a bad idea on the whole, and has irksome
               | disadvantages.
               | 
               | Lisp-2 also has irksome disadvantages, like the verbosity
               | in code working with functional arguments.
               | 
               | I want to give myself and my users the advantages of
               | Lisp-1 and Lisp-2, as well as ways to avoid their
               | respective disadvantages, so there is no way to get
               | around having some kind of combination that lets us work
               | in different styles.
        
         | bloaf wrote:
         | I checked the Rye homepage, and it has "dialects" which allow
         | more familiar math infix notation. In that sense it is very
         | tcl-y, tcl technically being a lisp.
         | 
         | Looking at their rather confusing looping mechanisms, they
         | probably could benefit from being a little _more_ tcl-y, since
         | tcl has some of the best looping semantics I 've worked with.
        
           | middayc wrote:
           | Any concrete feedback on looping mechanisms being confusing
           | is appreciated.
        
             | bloaf wrote:
             | Certainly:
             | 
             | I can tell the rye devs like the idea of everything being a
             | function. But in their very first "language basics"
             | section, they introduce assignment _not_ as a function
             | call, but as some kind of magic that happens when you have
             | colons in a name.
             | 
             | So when we get to the "looping" section, it is the first
             | time we have seen a colon-having-name outside the context
             | of assignment:
             | 
             | > loop 3 { ::i , prns i }
             | 
             | And it is explained that the above line of code is
             | "injecting" values for the code block to "pick up".
             | 
             | But right away this begs a number of questions:
             | 
             | * Why the double-colon? I would assume each loop-body-
             | evaluation happens in its own scope, and that we're not
             | creating global variables, so a single colon (:i) should be
             | sufficient, right?
             | 
             | * What are we doing, conceptually? Is the ::i meant to be
             | "a function which when given a value modifies its enclosing
             | scope to include the symbol i" or "an unassigned symbol
             | which the loop function will use to do something akin to
             | term-rewriting with?"
             | 
             | * Do we really need a symbol at all, or could we just have
             | a the point-free loop "loop 3 {prns}"?
             | 
             | * If we can't have the point free thing, is it because
             | somehow the injected value would end up "to the left" of
             | prns, if so, why would we want that?
             | 
             | * If we're doing something more like term rewriting, why
             | isn't the symbol given as a separate argument from the
             | body?
        
               | middayc wrote:
               | Rye is foremost a REBOL and REBOL has this notion of many
               | types of words at it's core:
               | 
               | `word` - regular word, can evaluate to value it's bound
               | to or call a funtion if bound to a function
               | 
               | `word: "value"` - set-word, assignment that you noticed
               | 
               | `probe :word` - get-word, always returns bound value,
               | doesn't call a function if it's bound to a function, in
               | Rye this is `?word`, because `:word` is left-set-word.
               | 
               | `'word` - literal word, evaluates to a word itself
               | 
               | etc ...
               | 
               | Rye adds even more word types. Rye also has left to right
               | flow so it adds left-set-word. In Rye all assigned words
               | with set-words are constants and they are used by
               | default. So we also need a "mod-word", that is the double
               | colon that you noticed, and left-mod-word. Because of
               | left-to-right flow Rye also has .op-words and |pipe-
               | words.
               | 
               | The logic around words, op-words and pipe-words ... I
               | tried to explain here:
               | 
               | https://ryelang.org/meet_rye/specifics/opwords/
               | 
               | Another pattern you noticed (good observation btw:) is
               | the idea of injected blocks that isn't used just for
               | loops, but also for conditionals, function bodies, HOF-
               | like functions etc ...
               | 
               | https://ryelang.org/meet_rye/specifics/injected_blocks/
               | 
               | All in all it is supposed to be a compact set of ideas
               | that fit together. Some are somewhat unusual.
        
               | bloaf wrote:
               | > Rye also has left to right flow so it adds left-set-
               | word. In Rye all assigned words with set-words are
               | constants and they are used by default. So we also need a
               | "mod-word", that is the double colon that you noticed,
               | and left-mod-word
               | 
               | So I would assume that the :i _is actually_ constant
               | within the loop body scope. That is, the loop function is
               | doing something like this:
               | 
               | ; i is not assigned in this scope
               | 
               | evaluate {1 :i, prns i}
               | 
               | evaluate {2 :i, prns i}
               | 
               | evaluate {3 :i, prns i}
               | 
               | ; i is still not assigned in this scope
               | 
               | But it sounds like you're telling me that :i would
               | actually escape the scope of the loop body and so it
               | needs to be modifiable or else the loop will break.
        
               | middayc wrote:
               | Yes, Rye follows REBOL in this case. Plain block
               | invocation doesn't create it's own scope / context. That
               | holds for do, if, either, loop, for, map, etc.
               | 
               | It would be costly to have this on by default. If you
               | want separation there are many ways to achieve it. Rye
               | has many functions related to contexts / scopes. For
               | creating contexts in multiple ways and evaluating code
               | inside contexts or with context as parent or isolated
               | context, etc.
               | 
               | And a lot of builtins directly accept anonymous functions
               | in place of blocks of code.
               | 
               | For example for loop also accepts function if you want
               | separation and don't mind the cost.                   for
               | { 1 2 3 } fn { x } { print x }         ; which can also
               | be written with fn1         for { 1 2 3 } fn1 { .print }
               | ; or latest experiment where we have syntax for 3
               | injected blocks          ; .() - same as "with"         ;
               | .[] - same as "reduce/with"         ; .{} - same as "fn1"
               | ; where it's already decided department from REBOL:
               | ; () is "do"         ; [] is "vals"         ; {} is
               | literal block          for { 1 2 3 } .{ .print }
        
               | kazinator wrote:
               | If you treat assignment as a function, then you have to
               | reify environments as run-time objects, whereby you
               | basically lose lexical scope.
               | 
               | Lisp originally, as in LISP, had assignment as a
               | function: it was called SET.
               | 
               | To use it, you usually had to quote: (SET 'VAR 42).
               | 
               | It worked without an environment parameter because
               | variables were in a pervasive environment, but the quote
               | was needed to get the variable symbol as a run-time
               | value. (SET VAR 42) would mean evaluate VAR to a symbol,
               | and then pass that symbol to the SET function along with
               | 42, so whatever variable was in VAR would be assigned.
               | 
               | Assignment is inherently non-functional, since it is a
               | side-effect, so it is mostly counterproductive to model
               | it as a function.
               | 
               | A pattern matching or logical language can have implicit
               | bindings as the results of an operation, and so produce
               | variables that way. Then instead of assignment you have
               | shadowing, in that some construct binds a variable again
               | that was already used, so that the most recent value then
               | emerges, shadowing the previous one.
        
               | bloaf wrote:
               | So tcl handles it somewhat more elegantly, I think. It
               | also has a set function, but does not require any special
               | quoting because it uses the $ prefix to denote "get the
               | value of this symbol":                   set a 5
               | puts $a  #prints 5
               | 
               | and of course because it is modeled as a function (albeit
               | an impure one) you can pass arguments to it as normal:
               | set a b         set $a 5  #equivalent to set b 5
               | puts $b   #prints 5
               | 
               | of course, everything in tcl is a string, so this works
               | too lol                   set a 5         set $a b
               | puts $5  #prints b
        
               | middayc wrote:
               | I've personally always thought that REBOL's use of set-
               | words and similar constructs was a strength. It makes
               | sense conceptually, is visually distinguishable, and
               | maintains a strong sense of internal consistency.
               | 
               | REBOL (and by extension, Rye) was never designed around
               | the idea that everything must be a function. It just
               | turns out that this approach fits naturally within the
               | core principles and rules of the language.
               | 
               | All "active" words happen to be functions, because
               | nothing else is needed. The behavior of different word
               | types (and, more broadly, value types) is determined by
               | the evaluator. In that sense, you could say that Rye does
               | have syntax, expressed through its distinct word types.
        
       | veqq wrote:
       | 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 ph 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 wrote:
         | 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 wrote:
           | 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!
        
       | icepat wrote:
       | Is this just a quirk in my display, or are all the code blocks in
       | this formatted like a CIA black highlighter
        
         | middayc wrote:
         | 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 wrote:
           | 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 wrote:
             | I fixed it thanks! I was able to activate dark more in
             | Firefox on desktop and find problems with CSS.
        
           | icepat wrote:
           | This is on FF for me. Specifically Zen, which is FF based.
        
             | middayc wrote:
             | You still have this problem? I tested and fixed css
             | yesterday. What exactly is problematic?
        
               | icepat wrote:
               | I hadn't checked since I posted this, but today the
               | entire document appears properly unredacted! Nice work.
        
         | singlow wrote:
         | 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 wrote:
         | Thank you all for heads up! I was playing with CSS and didn't
         | test the dark mode. I think it's fixed now.
        
       | jrochkind1 wrote:
       | Smalltalk, anyone? I guess the OO version.
        
         | sebastianconcpt wrote:
         | 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 wrote:
         | There is a language https://sprylang.se/index.html which
         | started more as Rebol and moved towards Smalltalk.
        
       | ivanjermakov wrote:
       | 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.
        
         | danlitt wrote:
         | This criticism seems at face value to also apply to first-class
         | functions, which I thought was a totally uncontroversial
         | pattern. Do you dislike those too?
        
           | ivanjermakov wrote:
           | First-class functions are problematic too[1], but function is
           | always a definition. While code block is usually meant to be
           | executed right away.
           | 
           | [1]: https://github.com/ziglang/zig/issues/1048
        
             | em-bee wrote:
             | can you please explain what the actual problem here is? i
             | am trying to read through that issue discussion, but i am
             | not quite getting what makes first-class functions
             | problematic. as far as i am concerned, not having first-
             | class functions would be a serious limitation to a language
             | that would make me avoid using that language for anything
             | serious.
        
       | hatthew wrote:
       | 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 wrote:
         | > 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 wrote:
           | That makes sense, thanks!
        
         | hshdhdhehd wrote:
         | 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.
        
           | middayc wrote:
           | You can do it, but that is not how the (default) control
           | structures work in those languages. There is usually also
           | some syntax cost.
        
             | hshdhdhehd wrote:
             | Thats a good point. Idiomatics are important and not
             | following them makes incompatible code.
        
           | andriamanitra wrote:
           | You may not want a fresh scope for control flow as you often
           | want to use variables from the outer scope inside the if
           | statement. Imagine you wanted to do something like this with
           | your if statement implemented with a function (this is how
           | the syntax would look like using a block argument in Ruby):
           | state = "inactive"         if_func(condition) {
           | state = "active"             activate_button.disabled = true
           | deactivate_button.disabled = false         }
           | 
           | In many languages you would need to wrap `state` in something
           | that can be passed by reference, and make the function take
           | multiple parameters. For example in JavaScript it would turn
           | into something like this mess:                   let state =
           | ["inactive"];         if_func(condition, ({state,
           | activate_button, deactivate_button}) => {
           | state[0] = "active";             activate_button.disabled =
           | true;             deactivate_button.disabled = false;
           | }, {state, activate_button, deactivate_button});
        
         | middayc wrote:
         | It depends on what you want. If you want the most stabile and
         | predictable way to specify the behavior, then static control
         | structures have little downsides.
         | 
         | If you want to explore with how you can specify behaviors or
         | rules and create new options or the ones tightly fitting your
         | problem domain or mental model, then this gives you more tools
         | to do so.
        
       | davidw wrote:
       | Seems a bit like Tcl, which lets you create your own control
       | structures like that.
        
         | middayc wrote:
         | 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 wrote:
         | Or redefine the language provided 'if' statement, in the case
         | that one wanted to do so.
        
           | middayc wrote:
           | You can't really redefine if because everything is a
           | constant, but you can define if in your own context yes.
        
             | cmacleod4 wrote:
             | In Tcl you _can_ redefine  "if", or even delete it entirely
             | if you're crazy enough :-)
        
       | spankalee wrote:
       | 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.
        
         | middayc wrote:
         | 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
        
         | iamevn wrote:
         | See the `either` function further down                 either
         | some-condition { print "was true" } { print "was false" }
        
       | analog31 wrote:
       | Interestingly, Excel provides "if" as a function:
       | 
       | =if(condition, value-if-true, value-if-false)
        
         | middayc wrote:
         | Yes, Io language is closest to that syntax probably:
         | 
         | https://iolanguage.org/guide/guide.html#Introduction
        
           | analog31 wrote:
           | I wonder if it's more readable to non-programmers than
           | control flow.
        
         | danlitt wrote:
         | That's because Excel is a pure functional language :-)
         | 
         | https://www.youtube.com/watch?v=0yKf8TrLUOw
        
       | lisbbb wrote:
       | This discussion makes me so happy because people still care about
       | programming languages and not just on stupid Java or whatever is
       | making gobs of money. LISP should have a much larger following
       | than it does, though I fully admit it has its own warts.
        
       | solomonb wrote:
       | 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
        
       | mappum wrote:
       | I own the 'if' package on npm, which I wrote to be functions that
       | can replace the if keyword, making no use of the if keyword in
       | its definition.
        
         | middayc wrote:
         | interesting. Give us an example of it's usage ... or how you
         | implemented it?
        
           | morcus wrote:
           | I looked at the code - it uses ternary expressions.
        
       | ozy wrote:
       | Useless unless the logical operators receive their rhs
       | unevaluated. And that is generalized as a language feature.
        
         | sparkie wrote:
         | 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 an _applicative_ by wrapping an operative.
         | ($define! $lambda             ($vau (args . body) env
         | (wrap (eval (list* $vau args #ignore body) env))))
         | 
         | All functions have an underlying operative which can be
         | extracted with _unwrap_.
         | 
         | [1]:https://ftp.cs.wpi.edu/pub/techreports/pdf/05-07.pdf
        
       | tromp wrote:
       | For lambda calculus, the motto is "when everything is a
       | function". The boolean true is the function lx.ly.x, while false
       | is lx.ly.y. If b then x else y then simply becomes b x y. In a
       | functional language like Haskell that is basically a typed lambda
       | calculus with lots of syntactic sugar, we can replicate this
       | with:                   type MyBool a = a -> a -> a
       | myTrue :: MyBool a         myTrue = \x y -> x         myFalse ::
       | MyBool a         myFalse = \x y -> y              myIf :: MyBool
       | a -> a -> a -> a         myIf b myThen myElse = b myThen myElse
       | main = print $ myIf myTrue "true" "false"
        
         | Y_Y wrote:
         | yourIf == myId
        
           | tromp wrote:
           | No instance for (Eq (MyBool a0 -> a0 -> a0 -> a0))
           | arising from a use of '=='
           | 
           | Undecidability of function equality aside, we could indeed
           | define "myIf = id" instead.
        
             | Y_Y wrote:
             | Fair point, though if I remeber correctly there is only one
             | function with type a->a and so we get equality
             | automatically.
             | 
             | (This may be untrue in the presence of the likes of
             | unsafeCoerce.)
        
               | tromp wrote:
               | There are other functions MyBool a -> MyBool a, such as
               | not = \b x y -> b y x.
        
         | nextaccountic wrote:
         | This is
         | https://en.wikipedia.org/wiki/Church_encoding#Church_Boolean...
         | 
         | But there are other encodings
        
           | tromp wrote:
           | This is both the Church encoding and the Scott encoding of
           | the abstract data type                   data Bool = True |
           | False
           | 
           | making it pretty much the only encoding you find in the
           | literature.
           | 
           | This is quite different from the case of the natural numbers,
           | where not only do the Church and Scott encoding differ, but
           | there are several other reasonable representations fitting
           | particular purposes.
        
       | motorest wrote:
       | I'm definitely missing some key insight because after reading the
       | article I felt it was a strawman argument.
       | 
       | Python already has conditional expressions, which already allow
       | 'x if (predicate) else y'. Therefore in Python if is already
       | equivalent to a function, and is composable.
       | 
       | Once you realize this, and also understand that Python has
       | logical operators that can short-circuit, all Python examples
       | feel convoluted and required the blogger to go way out of it's
       | way to write nonidiomatic Python. If the goal was to make a point
       | with Python, why not write Python?
        
         | middayc wrote:
         | Python was used as an example because people know Python and
         | reader can compare it to something known.
         | 
         | I wasn't trying to make Python look awkward. I was trying to
         | write equivalent Python, you are very much welcome to suggest
         | improvements to my Python examples.
         | 
         | Other languages could be used instead of Python, for example
         | Javascript. But I feel Python is more contained language, with
         | clear ways to do thing (I guess I don't know them that well :)
         | )
        
           | motorest wrote:
           | > Python was used as an example because people know Python
           | and reader can compare it to something known.
           | 
           | I don't think you understood the point I made.
           | 
           | My point is that Python supports conditional expressions for
           | years.
           | 
           | Support for conditional expressions already means that in
           | Python indeed "if is just a function".
           | 
           | Therefore, the whole premise of the article is null and void,
           | and the article is thus pointless.
           | 
           | This is evident to anyone who is familiar with Python. If you
           | have any experience with Python and you are familiar with
           | idiomatic Python and basic features such as list
           | comprehension, you are already widely aware how "if is just a
           | function".
           | 
           | > I wasn't trying to make Python look awkward. I was trying
           | to write equivalent Python (...)
           | 
           | Except you didn't. You failed to even acknowledge that Python
           | already supports "if is just a function" with basic aspects
           | such as list comprehension.
           | 
           | This basic aspect of Python is covered quite prominently in
           | intros to Python, but somehow you failed to even acknowledge
           | it exists, and proceeded to base your argument on a patently
           | false claim.
        
             | middayc wrote:
             | If Python has special syntax for `if` and so makes if an
             | expression, it doesn't mean that `if` is a function.
             | 
             | If `if` is a function why don't you call it like other
             | functions in Python?                   if( .... )
             | 
             | Functions are first class in Python now AFAIK. Can you
             | assign `if` to a variable?
             | 
             | Also, blog-posts is not just about `if`, is `for` a
             | function in Python, `def`, `return`, `class`?
        
       | correct_horse wrote:
       | Just here to point out that the actor's name is Bob Odenkirk not
       | Odendirk. In a statically typed language this would be an error
       | at compile time not hacker news comment time.
        
         | middayc wrote:
         | My bad :), Gilligan typed language would resolve this at
         | compile time. Will fix.
        
           | andrepd wrote:
           | Bravo, Vince.
        
         | matt123456789 wrote:
         | gcc() { curl -d '{"title": "Does this code look right?",
         | "body": "$(cat $1)"' https://$HN_BASE/api/ask }
         | 
         | export gcc
        
       | emoII wrote:
       | Interesting that this article makes no mention of eager vs lazy
       | evaluation - isn't a big reason that if, for etc has to be
       | special forms in an eagerly evaluated language that their
       | arguments need to be lazily evaluated, which of course, deviates
       | from the rule? Also, lazy evaluation is achieved in an eagerly
       | evaluated language as simply wrapping a block of code in a
       | function, which makes lazy evaluation isomorphic with the
       | contents of the article
        
         | conradludgate wrote:
         | > 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.
        
           | emoII wrote:
           | You're correct, that is lazy evaluation. The entire article
           | talks about lazy evaluation without mentioning it, which was
           | my point
        
             | middayc wrote:
             | I've been programming in REBOL for decade(s) so this is
             | just how it worked and made sense and we never used term
             | "lazy evaluation", so it not part of my vocabulary when
             | explaining this.
             | 
             | Blocks are not evaluated by default, but they are eagerly
             | evaluated if the function that accepts it decides to do so
             | (if, do, loop) ... I understand lazy evaluation more like
             | something that is meant to be evaluated, but physically
             | only gets evaluated when or if you do need the result,
             | which I'm not sure is entirely the same.
        
       | the_gipsy wrote:
       | When everything is an expression, like rust (I think), that's
       | enough. Most of the control flow are used as statements, but if
       | needed, they can be used directly as expression, and you get
       | basically all the advantages. After seeing how effective this is,
       | using a language that doesn't have this feature can be really
       | annoying.
       | 
       | On the other hand, I would like to explore "when arithmetics is
       | just a function". I think Elm does this well: operators are just
       | functions with two arguments that can be written as "1 + 2", the
       | familiar way, or "(+) 1 2". Then you can compose it like "map
       | ((+) 2)" (currying) so you get a function that adds 2 to every
       | item of a list, and so on.
        
         | middayc wrote:
         | This is another subject but mathematical operators are and
         | behave (for better and worse - there are also negative
         | consequences of this consistency) like all other op-words, they
         | just don't need . in front and correct, non-op-word is
         | prepended by _.                   12 + 23    ; is technically
         | 12 ._+ 23  ; or not using op-word is         _+ 12 23   ; or if
         | we bind function _+ to add         add: ?_+   ; we get
         | 12 .add 23 ; and         add 12 23
         | 
         | It has downside because op-words don't behave exactly as math
         | expressions would, but you can use parenthesis and we have a
         | math dialect which includes full math precedence rules.
         | math { 12 * 2 + 23 * 3 }
        
       | foofoo12 wrote:
       | The Trade-offs section doesn't list the biggest one.
       | 
       | Secretly, all code wants to be spaghetti. You and your team have
       | to put a conscious effort into prevent that from happening.
       | Degrading the core of the language like this is like inoculating
       | your homebrew with sewage and expecting it not to go wrong.
        
         | middayc wrote:
         | That is sort of like saying all visual art projects want to
         | become "the million dollar / pixel homepage" so providing an
         | empty canvas and full color palete will just enable people to
         | create visual sewage because nothing stops them from doing so.
         | 
         | I never programmed in a team, so my experience of programming
         | is probably very different from yours. You probably want
         | something like electric cattle fencing (if I borrow your juicy
         | language) for your team, but if I program for my self I just
         | want an open field of Rye I can explore :)
        
           | diegoperini wrote:
           | That analogy may not be suitable for this case because value
           | proposition between the aesthetics vs the function is
           | different for visual art projects compared to software. There
           | is also the maintainability factor where most aged software
           | (especially the closed source ones in private sector) change
           | maintainers every few years. Old maintainers most often lose
           | access to the source code and become unreachable after
           | leaving their job.
        
           | foofoo12 wrote:
           | By all means, go full avant-garde. I have nothing against
           | experimental stuff like this. The "throw it at the wall an
           | see what sticks" idea.
           | 
           | According to my own experience, it's entirely possible to
           | write a rancid spaghetti carbonara all by yourself. I'm not
           | saying you shouldn't do it (it's a heck of a learning
           | experience) or it should be banned or prevented or anything.
           | But if the language comes with a tin of e. coli, at least
           | list the side effects.
        
       | padjo wrote:
       | The apply* ?thing is kinda hard to parse, would turn me off
       | seeing lots of that in a codebase.
        
         | middayc wrote:
         | I agree. This is not something you would usually use to
         | program, at least not all three together, but as it's written
         | it's consistent and signals exactly what it does, to someone
         | that knows Rye conventions / rules.
         | 
         | I can break it down for you, but yes ... it's quite specific, I
         | was trying to apply an `if` which is not something I needed to
         | do or would look to do so far. The point is that you can also
         | apply all "control structure like" functions, like any other
         | function - consistency, not that this is advised or often used.
         | 
         | ?word is a get word. `x: inc 10` evaluates inc function, so x
         | is 11, but `x: ?inc` returns the inc function, so x is the
         | builtin function.
         | 
         | apply is a function that applies a function to a block of
         | arguments. It's usefull when you want to be creative, but it's
         | not really used in run of the mill code.
         | 
         | .apply (op-word of apply) takes first argument from the left.
         | `?print .apply [ "Hello" ]`
         | 
         | Here we needed to take second argument from the left and this
         | is what a * modifier at the end of the op- or pipe-word does.
         | `[ "hello" ] .apply* ?print`
         | 
         | You asked for it :P
        
       | 6gvONxR4sf7o wrote:
       | There's more to this that I'd absolutely love to see in a
       | language, and I can't tell if rye supports. If you want an
       | ergonomic `if` you need its scope aspects too.
       | 
       | Consider this example                   <beginning of the
       | function>         x = ...         if foo:            y = ...
       | else:            x = ...            y = ...         <rest of the
       | function>
       | 
       | Critical parts of the ergonomics are that
       | 
       | a) in each branch, we have everything in scope that comes from
       | <beginning of the function>
       | 
       | b) in <rest of the function>, we have everything in scope that
       | was assigned or reassigned in the executed branch
       | 
       | I'd love a language that supports programmable stuff like if,
       | since I'm tired of python autodiff not handling `if` and `for`.
       | But it would really need programmable scope stuff to still allow
       | the ergonomic "scope effects" that make `if` and `for` blocks
       | ergonomic.
        
       | Cosi1125 wrote:
       | In R:                   > `if` = function(a, b) { print(a);
       | print(b) }         > if (2 < 5) { 6 }         [1] TRUE
       | [1] 6
       | 
       | (Rye also looks like a nice language, though!)
        
       | skybrian wrote:
       | Using closures or code blocks when they're unnecessary gets in
       | the way of static analysis and (sometimes) readability. It's
       | often better to avoid using language constructs that are more
       | powerful than needed for the job. You can still use them when
       | doing something more advanced.
        
       ___________________________________________________________________
       (page generated 2025-10-18 23:02 UTC)