(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
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!
- 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.
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?)
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.
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.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.
For example file-path, url and email address are distinct types in REBOL where in mosta languages are just strings.
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 either some-condition { print "was true" } { print "was false" }
=if(condition, value-if-true, value-if-false)
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
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. 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")
blahgeek•9h ago
taeric•9h ago
middayc•8h ago
kazinator•7h ago
TXR Lisp: (relevant to this article) there is an iff function that takes functional arguments.
Square the odd values in 0 to 9:
The use function is a synonym of identity: i.e. just use the incoming value as-isSquare the even ones instead by inverting oddp with notf:
Get rid of use with iffi: a two-argument iff with an implicit identity else: Now about the point about Lisps and if: the regular if operator with value and expression arguments has a companion if function: 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:
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•2h ago
> 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.
bloaf•8h ago
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•8h ago
bloaf•7h ago
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•7h ago
`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•7h ago
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•6h ago
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.
kazinator•7h ago
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•6h ago
middayc•30m ago
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.