proj-oot-ootControlNotes1

i guess references mark bits of state that persist even if you invoke a continuation.

--

we gotta do something about foldl, foldr. having the user keep track of which one of these will lead to an error when they sum a bunch of numbers is like manual memory management.

http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27

http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29#Evaluation_order_considerations

http://en.wikibooks.org/wiki/Haskell/Performance_Introduction

ah, i think this is called "automatic strictness analysis".

--- the commments on this also suggest syntaxes etc for dealing with strictness:

http://pchiusano.blogspot.com/2009/06/perfect-strictness-analysis-part-2.html

--

https://www.google.com/search?client=ubuntu&channel=fs&q=foldl+foldr+automatic+strictness+analysis&ie=utf-8&oe=utf-8#bav=on.2,or.r_qf.&channel=fs&fp=48beb17d465ad959&q=+automatic+strictness+analysis

--

mb something like garbage collection for the stack? keep track of which parts of the stack resulted from which parts of code. if the stack is close to overflowing, then find a loop, and try switching folds (or whatever is causing the recursion) between foldl, foldl', foldr, foldr'

--

is clojure doseq an example of this powerful macro thang that everyone talks about? the test is whether or not it needs to execute before its arguments are evaluated (to rearrange the symbols). i guess you have to the pattern matching at the symbolic level, so maybe so.

interesting, todo: think about that.

--

" The Conduit library is a second generation approach to the problem of guaranteeing bounded memory usage in the presence of lazy evaluation. The first generation of these ideas were libraries like Iteratee, Enumerator, and IterIO?. All of these first generation libraries use the the term enumerator for data producers and iteratee for data consumers. The new Conduit library calls data producers "sources" and data consumers "sinks" to make them a little more approachable.

The other big difference between Conduit and the early libraries in this space is to do with guaranteeing early clean up of potentially scarce resources like sockets. Although I have not looked in any detail at the IterIO? library, both Iteratee and Enumerator simply rely on Haskell's garbage collector to clean up resources when they are no longer required. The Conduit library on the other hand uses Resource transformers to guarantee release of these resources as soon as possible. " http://www.mega-nerd.com/erikd/Blog/CodeHacking/Haskell/telnet-conduit.html

--

" no stack traces

This is less of a bother than one would think when coming from another language, but still a problem. Simon Marlow recently stated he may be able to come up with a solution, which was very exciting news. Yesod now supports logging statements that contain file and line number information using Template Haskell. I released a package that applies the same technique. This allows the programmer to record the file and line number location, but not a stack trace. There is also a package that is designed to give a stack trace for an exception. "

--

named break and named continue

"long after Dijkstra's essay, we could still occasionally find new places where goto was really the best way to do it: for instance, if you wanted to "break" out of multiple loops at once. So someone invented named break (likewise continue), and removed yet another use of the unconstrained goto in favour of a more constrained, structured construct."

--

"

wrl 7 hours ago

link

On the contrary, there are still perfectly good uses for goto. The two I can name right off the top of my head are stack-like error unwinding in C (which comes with an endorsement from CERT recommending its use) and computed goto dispatch tables in threaded interpreters.

"

---

hmm.. thinking about how the exception-like, antibody-like, event-driven-like 'backwards computation mode' would fit in with things.. how much is the handler tree distinct from the call stack?

could unify the conception of call stack and handler tree. e.g. so a return from a function is a message propagating upwards that gets immediately caught. wouldn't want to implement it that way though as you dont want returning from a function to be the least bit inefficient (esp. in a functional-ish language) -- so under the covers that common case would be optimized to a normal fn return.

also want to be able to have multiple handler trees, so e.g. you can have the call stack plus some sort of spreadsheet event-driven handler tree plus a GUI element handler tree.

as a special case of that, we might have those multiple threads of control in a single function, with multiple 'returns'

and of course note that handlers and handler trees are first-class objects.

---

hmm, reading about dataflow architectures, where "When all of the tagged operands of an instruction become available (that is, output from previous instructions and/or user input), the instruction is marked as ready for execution by an execution unit." -- http://en.wikipedia.org/wiki/Dataflow_architecture . There is also "Dataflow is a software architecture based on the idea that changing the value of a variable should automatically force recalculation of the values of variables which depend on its value. Dataflow embodies these principles, with spreadsheets perhaps the most widespread embodiment of dataflow. " -- http://en.wikipedia.org/wiki/Dataflow . Interesting that dataflow, the hardware architecture, is backwards looking (like promises), and dataflow, the software architecture, is forward looking (like event-driven programming). A more major different in those two seems to be that dataflow, the hardware arch, seems to be about how do you sequence a set of steps in a computation which may only be run once, whereas dataflow, the software arch, seems to be about how do you implicitly maintain invariants relating multiple pieces of state which may presumably lead to the need to redo a calculation many times.

and of course the bigger distinction it between either of these, and typical programming.

reminds me of the eager/lazy distinction, and also of promises, and also of multiple entry point functions, and also of event-driven programming.

should unify these.

--

also, with the problem of foldl and foldr and stack overflows due to unevaluated thunks, my intutition is to explore:

(a) a way for the programmer to specify that certain pieces of INPUT data are 'eager', e.g. any functions applied to that data are executed eagerly. This is because one of the main reason for having all functions be 'lazy by default' is to use infinite data structures; but if the user of a function knows that the input is not infinite, they can tell the function to go ahead and be eager on that input. Note that this eagerness is carried forward recursively (as opposed to Haskell's eagerness via seq, which is propagated backwards somewhat recursively, and deepseq, which is even more so; although i think we need seq and deepseq also) (b) note that even a function which takes all finite (and eager) input may not want to work eagerly and produce all eager outputs, however; e.g. consider a function which produces an infinite sequence from a constant input. (c) a sort of TCO for lazy functions/data; in the case of summing a large list via fold, the compiler should be able to realize that the thunks will ultimately be consumed (e.g. that 'if this value is requested, then that implies that ultimately that one will be too'), and evaluate them strictly. in fact, even in cases where the compiler cannot deduce this, the programmer should have a way to annotate the function (or parts of it) and specify that. (d) should we encourage the habit (and make the standard libraries this way) of annotating so that any function which, when given eager inputs, produces eager outputs without the possibility of more than constant unevaluated thunks in the middle, does? e.g. "fold op list" would look at list, and if list is marked as eager, it should use an eager foldl.

An argument against (d) might be, what if we want to use lazy lists not because we are dealing with an infinite list, but just a very long one? But we aren't seeing if the inputs are finite, we are seeing if they are marked eager, which is a choice made by the caller (or whoever called the caller, etc). So if the caller doesn't want this, they won't mark the list as eager.

i think the answer to (d) is "yes". in fact, i think that by default, we should assume that function that is given all 'eager' arguments will not produce a lazy result unless it declares that it does; and hence, in that case, the compiler or interpreter should always evaluate the function as soon as it can (e.g. whenever it is at the head of a thunk; this is like replacing all calls to the func with 'func `seq` func' in haskell, i think). if a function in addition wants to declare that it still gives an eager result even if some particular inputs are lazy (e.g. f x y = y + (head x) can produce an eager result even if x is lazy), then it can if it wants to, but this is not required.

A note on (c); http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal-form says "GHC has a strictness analyzer that will detect some situations where a subexpression is always needed and it can then evaluate it ahead of time. This is only an optimization, however, and you should not rely on it to save you from overflows". We want something similar, but like TCO, we want the language to GUARANTEE that certain things will be evaluated strictly.

--

so, regarding (d), a proposal:

mark data as 'eager' or 'lazy'.

eager data should be data that, if it is found inside a thunk, can safely be (and should be) immediately recursively evaluated (deepseq in haskell)

the programmer can manually change the flag of data to eager or to lazy

eagerness/laziness is propagated forward, from function input to function output, as computation is done.

a function can declare its outputs as eager or lazy. an eager output means that, if the function is not given any inputs marked lazy, then that output will be marked eager. by default all outputs are eager, that is, we assume that all funcs return all 'eager' return values if it is not given any lazy inputs.

any lazy-outputting function performed on all eager values are performed immediately (e.g. as soon as the function is at the head of a thunk, it is further evaluated if this condition is true of its inputs), but any function performed on a lazy value generates a thunk (unless the function declares that it can accept a lazy input on that input and still run eagerly).

it is a mistake for a programmer to fail to declare a function 'lazy' that can produce an infinite result from all finite inputs. such a function can cause an infinite loop/stack overflow when it returns, as the intepreter tries to immediately recursively evaluate its result. however really this declaration facility is just a convenience, because the caller of a function can cause it to execute lazily by providing a lazy input (so, for example, if we have a function that produces the entire (finite but very large) lookahead for a game of chess given the current position, it could declare its output lazy (on the assumption that no one would ever actually want the full table), or whoever calls it could coerce the input that they are feeding to it (e.g. the current position) to lazy).

the lazy declaration can use a pattern, e.g. a struct that contains both eager and lazy subcomponents (e.g. if function f(x) returns a struct Y with two fields, a and b, such that Y.a = sum(x) and Y.b = [0...], then (if x is eager), Y.a should be eager and Y.b should be lazy).

in practice, is this usually much different from clojure's lazy-seq macro and lazy- similar, which form a lazy iterator over code that returns a list? http://clojure.org/lazy ?

Haskell's fibonacchi sequence:

fib = 1 : 1 : zipWith + (tail fib)

can be written in Clojure:

(def fib (lazy-cat [1 1] (map + (rest fib) fib)))

hmm, this post: http://debasishg.blogspot.com/2010/05/laziness-in-clojure-some-thoughts.html

points out that if you have a large list, and you filter it, and then you map over the result, without laziness you iterate over the entire list twice, but with laziness, only once. So you will end up wanting to do imperative looping if you don't have laziness.

hmm.. it seems that filter isn't a 'dangerous' lazy function in the way that foldr is, because it doesn't require stack space proportional to the length of the list.

maybe we need to combine the above proposal with the "TCO" type thing; the evaluation of the supposedly 'eager' result happens only if some sort of TCO-like analysis determines that we're effectivlely forming thunks of thunks recursively via tail calls

i dunno how any of that would help if the user tries to do

> foldr f z [] = z > foldr f z (x:xs) = x `f` foldr f z xs

> sum1 = foldr (+) 0

> try1 = sum1 veryBigList

as http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 shows, the way the parentheses are placed, you never get two integers next to each other in the same parens; you always get something like 1 + (2 + (3 + (foldr (+) 0 [4..1000000]))), so you have to recurse on foldr before doing any addition.

perhaps the solution there is to declare functions as associative, so that the compiler could add 1+2+3 in this case (assuming we have the other eagerness stuff above). alternately, the TCO-ish stuff could somehow realize that this function is like a loop (well, i guess that requires knowing the associativity, actually. alternately/in addition we could put this big thunk on the heap instead of on the stack, so at least it only takes up memory, rather than crashing. alternately, we could do the analysis specified in http://www.haskell.org/haskellwiki/Stack_overflow for the user to choose between foldl, foldr, foldl', foldr': "A function strict in its second argument will always require linear stack space with foldr, so foldl' should be used instead in that case. If the function is lazy/non-strict in its second argument we should use foldr to 1) support infinite lists and 2) to allow a streaming use of the input list where only part of it needs to be in memory at a time. Okay, both here and in the one-line summary, there is no mention of foldl. When should foldl be used? The pragmatic answer is: by and far, it shouldn't be used. A case where it makes a difference is if the function is conditionally strict in its first argument depending on its second, where I use conditionally strict to mean a function that is strict or not in one argument depending on another argument(s)."

in order to support the kind of reasoning in the last alternative, we need to distinguish between two kinds of laziness in our declarations: (a) if we are trying to evaluate the function but we may not have all of the arguments yet, which arguments does the function need to be evaluated before it can completely execute? (you might replace this with the other defn from btw http://www.haskell.org/haskellwiki/Stack_overflow concerning the possibility of nontermination, see below) (b) if we have evaluated the function and it has produced a result, will that result be infinite?

--

btw http://www.haskell.org/haskellwiki/Stack_overflow has another defn of strictness:

"* A strict function is a function f , such that f⊥ = ⊥.

Typically, we think of a function "being strict" in an argument as a function that "forces" its argument, but the above definition of strict should immediately suggest another function that is strict and doesn't "force" it's argument in the intuitive sense, namely id. ([]++) = id and therefore is a strict function. Sure enough, if you were to evaluate (concat (repeat [])) it would not terminate. As such, (++) is a conditionally strict function. This also makes the "always" slightly imprecise, a function that is strict because it just returns it's argument, will not use up stack space (but is, as mentioned, still an issue for infinitely long lists). "

-- in any case, wouldn't the Clojure example from http://debasishg.blogspot.com/2010/05/laziness-in-clojure-some-thoughts.html , where 'filter' and 'map' are composed in a way such that the list is only iterated over once, not work unless they are foldr-ish? i guess that's because 'filter' is "lazy/non-strict in its second argument" as http://www.haskell.org/haskellwiki/Stack_overflow puts it.

--

btw it seems that TCO-like strictness analysis should definitely be able to coerce foldl to foldl' in http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal-form

--

ok this is crazy too:

http://www.haskell.org/haskellwiki/Stack_overflow " Common newbie stack overflowing code:

myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)

People who understand seq and weak head normal form (whnf) can immediately understand what goes wrong here. (acc+x, len+1) is already in whnf, so the seq (in the definition of foldl' ), which reduces a value to whnf, does nothing to this. This code will build up thunks just like the original foldl example, they'll just be inside a tuple. "

yarg, what do we do about that? haskell would let you make a 'strict tuple' and use that here. but that's just the kind of thinking that we want to avoid making the user do.

if we really have to do that, then maybe clojure soln of limiting laziness to data structures that explicitly support it is better; e.g. tuple etc would be strict by default.

again, maybe strictness analysis could determine that we will eventually need to reduce these guys and so would reduce them immediately

--

also dont forget my idea about doing something like 'garbage collection' on large thunks

dunno if that mixes with just putting them on the heap

btw in a language like haskell where you use folds for iteration, i guess it would be way inefficient to put this stuff on the heap (but maybe only move it there if it gets too big?)

---

and then what about this:

http://www.haskell.org/haskellwiki/Stack_overflow " Scans

A subtle stack-overflow surprise comes when

print (scanl (+) 0 [1..1000000])

completes successfully but

print (last (scanl (+) 0 [1..1000000]))

causes a stack overflow.

The latter stack overflow is explained exactly as before, namely,

last (scanl (+) 0 [1..5]) -> ... several steps ... -> ((((0+1)+2)+3)+4)+5

This is exactly like foldl , building a deep thunk, then evaluating, needing much stack.

Most puzzling is why the former succeeds without a stack overflow. This is caused by a combination of two factors:

    thunks in the list produced by
    scanl
    enjoy sharing: late thunks build upon early thunks
    printing a list of numbers evaluates early thunks and then late thunks "

i guess it's not really any more of a problem than the previous..

-- also, what about this one?

http://www.haskell.org/haskellwiki/Performance/Accumulating_parameter

first, we have the 'almost tail recursive' and we have to use an accumulating parameter to make it tail recursive. second, we have the same strictness problem as with foldl, above.

shouldn't the compiler do the 'almost tail recursive' transformation for us?

this seems like the foldl vs foldr problem.

using the naive implementation in the above blog post we'll get a stack that looks like 1 + (1 + (1 + (len xs)))

using the tail-recursive version we get instead len xs (1 + 1 + 1)

looking at the accumulating vs. naive implementation in the blog post, we see that the naive one has a call "len xs + 1" and the accumulating one has "len' xs (1 + acc)". So we started with one where the recursive call was buried under the '+', and we just 'bubbled it up' to the top level of the AST. But we need the associativity of the operations 'above' the recursive call in the AST in order to bubble up the recursive call to the top, as you can see by looking at the two expansions above.

--

this is a great read for Haskell's evaluation strategy:

http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal-form

--

related: GHC also has "bang pattern bindings", which means you prefix an exclamation point to a variable in a pattern, and then it's like 'seq' was applied to the assignment to that variable (e.g. to the value being assigned just before it is assigned)

--

http://www.haskell.org/haskellwiki/Stack_overflow

http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27

'stack_overflow' is a better read but 'Foldr_Foldl_Foldl%27' is easier to read

" There is no call stack in Haskell. Instead we find a pattern matching stack whose entries are essentially case expressions waiting for their scrutinee to be evaluated enough that they can match a constructor (WHNF).

When GHC is evaluating a thunked expression it uses an internal stack. This inner stack for thunk evaluation is the one that can overflow in practice. "

--

in fact, Oot should allow one to program in an 'active data structure' mode if one wishes (e.g. one specifies for various pieces of data what these pieces of data do, that is, how they combine/interact with their neighbors to produce new data, and what that data does -- i guess this would be programmed like stem cell differentiation in tissue development!)

--

"

2 Controversy!

Note that seq is the only way to force evaluation of a value with a function type (except by applying it, which is liable to cause other problems). As such, it is the only reason why Haskell programs are able to distinguish between the following two values:

undefined :: a -> b const undefined :: a -> b

This violates the principle from lambda calculus of extensionality of functions, or eta-conversion, because f and \x -> f x are distinct functions, even though they return the same output for every input. For this reason, seq, and this distinction, is sometimes ignored e.g. when assessing the correctness of optimisation techniques or type class instances. "

--

i guess there are 2 differences between dataflow spreadsheets and lazy evaluation:

in lazy evaluation, a function's inputs are parameters

in lazy evaluation, a function is executed just when it is needed by another function (top-down propagation of need)

in dataflow, a function (or rather, an object to which a function is attached)'s inputs are pieces of state

in dataflow, a function (or rather, an object to which a function is attached) is executed when its input states change

however, one could also imagine a lazy spreadsheet, who wouldn't reexecute an object unless both its inputs changed, and its value was desired

some spreadsheet cells might be lazy in some of their inputs and eager in others, in that they execute immediately when the eager inputs change

you could represent the spreadsheet by calling methods to attach listeners, but it would be neater and probably more concise to represent it as a graph where 'a listens to b' is 'a -> b'

in fact it's as if each node had a handler tree for its changes. but if we're going to go there, don't we want each node to be able to spout off various messages more descriptive than 'i changed'? and then aren't we like smalltalk? not that that's bad.

maybe dataflow is a special case of actors, one in which the actors are just containers for a value and the only messages are 'what is your value' and 'my value changed' (and pub/sub, i guess, although in our model that's outside the individual actors).

--

forwhile loop (my idea): do a (possibly nested) for loop, but set flag and 'break' (all the way out) if condition is true; at the end, do one thing if condition was true, another thing otherwise

bounded while loop (my idea): a 'safe' while loop with a max number of iterations, also a minimum amount of 'progress' (percentage or abs change in any/all of various variables) (e.g. for writing numerical iterative algorithms that have an escape hatch to ensure termination in this fashion)

---

http://users.aber.ac.uk/afc/stricthaskell.html

--

great essay on the problems with laziness:

http://www.megacz.com/thoughts/on.laziness.html

in summary:

the bit about concurrency: " Minor: Full Abstraction / Concurrency

Laziness breaks full abstraction, and adding in the necessary operator to restore full abstraction forces you into accepting a sort of crippled concurrency. People often forget that lazy PCF isn't fully abstract without an interleaving operator. This operator is either missing or else a hacky second-class citizen in all implementations.

Lazy PCF can in fact be used as a semantics for concurrency (for example, Kahn process networks use basically the same non-flat denotational domain as lazy PCF), but the more I learn about this the less I think that it's a complete semantics for concurrency. If you've decided that you want to have concurrency, you probably want operators like “fair merge” which have no denotational semantics in the lazy PCF domain. So I think that “models concurrency well” is a terrible argument for laziness. "

--

https://github.com/dmbarbour/Sirea

http://awelonblue.wordpress.com/about/

https://github.com/dmbarbour/awelon

" Wednesday, September 5, 2012 Reactive Demand Programming David Barbour has created a very promising and exciting paradigm for writing interactive, networked applications: Reactive Demand Programming (RDP).

RDP is very sophisticated and I can't really do it justice here, but its salient points are:

    An RDP application is a dynamically-changing set of semi-permanent, bidirectional data exchanges, called behaviors. What pipes are to Unix, behaviors are to RDP.
    A signal is a monodirectional channel carrying a value, and varies discretely over time.
    A behavior is made up of one or more input signals, called demands, and a single output signal.
    RDP builds in the notion of anticipation - every signal update comes with the expected duration the new value is valid. This allows low latency through smart scheduling.
    [Update: See David's corrections in the comments.]

An example for a behavior would be a camera receiving move and zoom commands (or rather demands) with discrete time intervals (e.g. as long as a user moves the camera joystick) from one or more users on input signals, interpolating these commands using some deterministic decision procedure (such as averaging all commands), and outputting camera frames on the output signal, with the anticipation measure telling clients something about the rate at which the camera refreshes, allowing them to smartly perform display reprocessing.

The best way to get started is the README of David's RDP implementation. David has also just posted a progress report on his blog, which contains many articles on RDP.

I think RDP is one of the most exciting developments in interactive application development and is worth checking out.

..

dmbarbour said...

    I'm glad to have your interest, Manuel. As I've done a poor job communicating about RDP, I'll try to clarify a few things:
    Rather than "many input signals, one output signal", a behavior may have a distinct output signal for each distinct input signal. Much like a function can have a distinct value for each input. A behavior corresponds closely to a function. RDP behaviors are not pure functions, but far more restricted in side-effects than a typical functional/imperative hybrid. The constraints on effects are such that behaviors can be treated very much like pure functions, e.g. with respect to commuting expressions or eliminating duplicate expressions. One thing you can do with pure functions that you cannot do with RDP behaviors is completely eliminate a behavior just because you drop the output.
    The essential property of RDP is enforcing declarative effects while (unlike most logic and concurrent constraint paradigms) also supporting procedural abstraction and encapsulation. This puts RDP at a very sweet spot between logic and procedural. Dynamic behaviors allow RDP an OO-like aspect, staged binding of resources. (Staged is somewhere between early binding and late binding. I like to call it `punctual` binding. :)
    Via declarative effects, multiple input signals to a behavior can interact. It is possible, for example, to combine a set of signals into signal of sets - a useful pattern that became the basis for demand monitors.
    Signals for RDP don't need to vary discretely. But discrete-varying signals are a helluva lot easier to implement and reason about. Still, I plan to eventually support a limited subset of continuous-varying signals, e.g. via trigonometric polynomials. (Right now, I lean towards modeling continuous signals as a layer above the discrete-varying signals.)
    I had not thought of signals as channels before. I guess that may be useful, since there is a tight correspondence in RDP (signal updates are necessarily communicated via some sort of data plumbing).
    I agree that anticipation is a significant feature of RDP. It reduces latency and synchronization overheads. I can actually anticipate more than one future value at a time, which (assuming a system that is predictable for a few seconds at a time) can potentially save an order of magnitude in communication and processing costs - i.e. I can tell you about the next ten updates, then correct you if I later realize I was wrong about the seventh update, and thus benefit from reduced communication and batched computations. It's also valuable for chained composition of constraint, planning or prediction systems, and resource acquisition.
    Best,
    David
    Wed Sep 05, 08:05:00 PM GMT+2 Manuel Simoni said...
    Could you please explain how a programmer can make a behavior return different independent outputs for different inputs?
    Wed Sep 05, 11:36:00 PM GMT+2 dmbarbour said...
    An RDP programmer does not need to do anything special to make a behavior return distinct outputs for distinct inputs. Consider two cases:
    1) A simple behavior `bfmap (+ 1)` takes the input signal and adds one to produce an output signal. Clearly, this behavior can be used in any number of locations, and every distinct input signal will generate a distinct output signal. (`bfmap` is one of many primitive constructors for behaviors provided by Sirea.)
    2) A behavior representing access to a filesystem resource (perhaps a particular directory) may allow a developer to provide an input signal containing a filename, and respond with an an output signal containing that file's contents (which might change over time). Obviously, it is essential that each distinct filename produce the appropriate file contents (which may be distinct), even if there are many concurrent requests.
    I wonder if we're speaking past one another. What is it you mean by `many input signals`? I suppose it could refer to a complex signal like (x :&: (y :|: z). But I think this is not the case.
    For RDP, it hugely helps to distinguish behaviors from resources. Resources (including sensors, actuators, state, demand monitors, etc.) exist outside of RDP. Some behaviors may provide access to external resources. If you use a behavior that accesses a resource multiple times, you do not replicate the resource; you just end up accessing the same resource many times.
    Resources like a filesystem can provide a distinct response to each input because, when setting up the signal connection for the demand, one also sets up a signal connection for the response. Demand and response are tightly coupled in RDP (modulo dead-code optimizations).
    If we want to guarantee that every signal has the same response, we can use a unit signal. Since all active unit signals are the same, at least in the short term, it is very easy to reason that all the responses should be the same.
    Thu Sep 06, 08:44:00 AM GMT+2 Manuel Simoni said...
    "What is it you mean by `many input signals`?"
    For example, the inputs from the camera operators' joysticks.
    Thu Sep 06, 11:13:00 AM GMT+2 dmbarbour said...
    Yes, that is what I understood. Did the prior explanation help?
    Taking your camera example in particular: a camera can have more than one behavior associated with it - e.g. one behavior to control the actuation (pan, tilt, zoom) and another behavior to request the video stream.
    Obviously, most simple cameras cannot return different video streams to different clients. Well, I suppose one could request variations like down-sampling the resolution or update frequency. But, assuming a simple camera model that only replies with one video signal to all active clients... makes a rather poor example if you want to see a different response for each client!
    Even assuming all active clients effectively receive the same video signal from the camera, one must think of this as a duplicated signal - i.e. each client can (via distinct behaviors composed with the video acquisition) manipulate the signal through different post-processing filters and deliver the video to different monitors.
    The control signals are a different story. As you mentioned earlier, one option is to `average` the requests to influence the camera. Another option is to respond to some control signals with "unable to comply". Note that the response to the control signal doesn't need to be a video stream.
    (Note: "averaging" demands is not preferred in RDP. For idempotence, duplicate demands must not be counted twice. So if you want to average, you must ensure there are no duplicate demands - e.g. by including with each demand a unique URL identifying the client. Similar idioms apply to tallying votes.)
    Thu Sep 06, 04:48:00 PM GMT+2 

"

--

extensible primitive types: unboxed stuff like int, string, float could be thought of inscrutable 'atoms', which are treated in a uniform way, and which can have primitive 'operations', which are platform primitives or external libraries, attached to them

this also allows us to represent zero-ary functions, by having the 'atom' operator serve as 'suspension' when applied to a Oot function

--

a selection primitive:

i guess either

Ghc core seems to choose case. GHC core uses trick of passing types explicitly as parameters to use case for polymorphic dispatch too. We could use typeOf for that.

i'm thinking we want to use 'get' rather than case, because it's more graph-y.

--

--

http://en.wikipedia.org/wiki/Conditional_(computer_programming)

http://users.dickinson.edu/~wahlst/356/ch8.pdf

--

one instantiation of the generalization of list comprehensions could even be a parallel control structure; a large number of threads are generated, in each one, potential actions are randomly Created by some function, then Transformed by another (or even executed within an undo-able transaction), and then Filtered by a third; actions which survive the Filter are actually executed (or, if there was an undo-able transaction, committed)

the Creation per-thread could be random (a generate-and-test stochastic algorithm), or deterministic concurrent (differing per thread in a deterministic way given thread identity; such a computation could also be executed in serial in one thread like an ordinary Python list comprehension) or deterministic fully serial (either the subsequent item depends on either all previous items) or deterministic accumulator serial (the subsequent item depends on the state of some accumulator variable, e.g. this is like a fold, and Transform might be called Integrate)

or, sequencing could be based on the middle step (Integrate/Pattern), rather than the Create step.

--

there's a similarity between macro application, logic-based production rule firing, and PEG grammar application, in that in these cases you have a control structure that looks over the thing to be matched (a state), applies a set of matching rules to it in order to find a potential match, and then mutates the state according to what the rule tells you to do, which may be a function of the part of state which matched

--

in golang, if you want to write an infinite loop, you use 'for' with no loop condition (True is the default loop condition):

https://tour.golang.org/flowcontrol/4

---

obviously, control flow constructs such as 'if' should be expressions, ie return values, and the last line of a fn is its reeturn value

---

calling a function could be just like beginning a conversation with a subprocess; instead of just returning a single return argument, there is a channel between the caller and the callee in which they can communicate. Unify functional reactive programming, forking off a subprocess, ordinary function returns, exceptions, 'reverse messages', 'anti-bodies'/event-driven programming/emit event, coroutines, generators, streams, lazy lists, codata/corecursion, asyncio

a return is just an 'emit' followed by a 'goto' back to the caller (or, i guess, since the caller is already running asynchrously, a halt instead of a goto)

structured messages a la what i imagine powershell to be is like the optional return arguments/destructuring bind;

a, b, name1/c = f x

means that x can send a struct back and the first element gets assigned to a, the second to b, and the element named 'name1' gets assigned to c

but how to allow some or all of these to be streams sometimes and individual values other times? or are they always streams? should we have a special sigil to denote which return arguments expect streams and which expect single values?

also, do we really want functions to be little state-holding objects, ie closures? i thought we wanted immutability.. note that a lazy list is immutable even if the thing generating it has state, b/c if you pass the list to someone else, the new guy can still see the beginning of it, eg the state is only relevant in the way that a cache is. Perhaps everything can work like that. (that's probably why they call it FUNCTIONAL reactive programming)

also, if everything is a pipe/channel, how does this square with the idea that calling a function is just substitution?

and how to make map/reduce easy/primitive too?

need syntax to say 'start computing what you need to in order to get this variable'

a fn is allowed to (a) yield return values without terminating (eg continue on executing after it 'returns' the desired values; eg forking off a subprocess), (b) yield parts of return values incrementally (eg like a generator coroutine)

special error/nil value for "this value isnt rady yet" and for "havent eevn begun to compute this yet, b/c lazy" --by default it blocks when u look at a value so u dont see thse, you havee to request them. note that any subsub part of a struct can be uncomputeed while the rest are computed -- need to use reecursive any' to check this (rany? or maybe any(flatten(x))?

fns can return things (post messagees/events) to ur callers, too, bypassing u. This is done like throwing an exception, except you just throw a non-exception message, to a topic that the caller isn't listening on. for security, you can turn this off: as with exceptions, caller ("owner"/parent) can "catch" child messages to prevent them from reaching grandparents

are handler trees also 'message buses' with topics to subscribe to? are channels? is a handler tree a form of subscriber? topics and channeels, like nsq? sort of. When a caller 'catches' a message from a callee, it's like they gave the callee a special message bus to use, one that passes messages through the current handler tree (call stack) rather than just posting them to the topic for everyone to immediately read.

---

versioned set of mutations coming from ("emitted"?) an object can be reified into a functional reacive stream. this allows versioning and undo, but also assertions of the form "evntually this will happen" (where "this" can be a certain mutation coming out, or more generally, a pure function on the whole stream up to this point taking on some value, eg indicating thee presenc of a certain wholistic state somewhere else)

so, SIDEFFECTS ARE ALSO JUST OUTPUT STREAM ELEMENTS

caller of a sideeffectful fn can "catch" sideeffects to prevent them from taking effect (mb this is the default, without the "!" o;erator, except on the log and cache and debug "topics"? no, that's too bug-ridden; a side-effectful function must be called with '!' to do the side-effect, or you can use 'eval' to get the stream out of it)

so {} are like "quote". is "!" like "unquote" or is it "pass on the sideeffects"? in some sense i guess sideeffects are themselves "quoted" in a functional reactive stream. preefix vs postfix "!" to apply to the thing itslf, or to its first return value? what about impure reads (not sideeffectful)? what about sideeffecttful reads, the rest of the stream cannot be produced until these are executed against some environment? hmm.. "!" as meaning "excute against the current environment"; mb "f!e" for "exec f against environ e"; exec means not just reading from the environ, but also writing to it.. environ can "catch" these writes (and reads!) if it wants, in order to produce a functional reeactive stream instead

---

mb first-class stack manipulations are like signals sent thru the call stack, in the same manner as exceptions (eg signals like SIGKILL, instead of ordinary messages)

---

Draining the Swamp: Micro Virtual Machines as Solid Foundation for Language Development refers to 'threads' as 'execution engines' and 'stacks' as 'execution contexts'.

The stack being part of (or all of) the 'execution context' is an interesting point. I had been using the word 'environment' to denote a namespace of variables, and their current bindings. But this is not the only 'context' in which a function runs; there is also the stack. In some cases, the stack might be thought to contain the environment, because all local variables, and all upvalues, are found somewhere in the stack. Are there other parts of 'execution context' besides the stack and the environment? I guess there is the PC and registers (which are kind of low-level implementation details, although i guess the PC at least is the same sort of thing as the stack); there are things like which language is being used, and dialect-changing parameters given to the runtime; there is per-run 'parameters' such as the mapping of OS filestreams to STDIN, STDOUT; there is the state of the 'external' world, including shared memory, disks, databases, the state of consoles (this relates to the read event stream and the side-effect (write) event stream); and there is pseudo-external world state, such as STM (software transactional memory) within the language runtime.

---

allow the conditional test expr in conditionals to be a block with multiple lines; the value of the last line is the value tested.

---

J's selection of 9 control structures is an excellent start (except that there should be a 'default' case in 'select')

9 control structures [1]:

break/continue are available.

---

async/await vs goroutines

is 'async' just saying that the thing should be executed asynchronously, but making this decision at the time of function declaration, rather than at the callsite (like goroutine "go")? No, i think; 'async' is declaring that the function might CONTAIN 'await's and so it might block or itself suspend while 'await'ing something else.

---

http://www.eighty-twenty.org/2014/11/11/thoughts-on-common-lisp likes exceptions on end-of-stream. Do we like that? Or are exceptions only for when a contract is violated? (although i guess the contract could be 'here's another item that i read from the stream')

---

(this paragraph already added to plbook): why can't Python use RAII for e.g. definitely closing a file handle when it goes out of scope? Because Python's garbage collection is non-deterministic; it is not guarantees that objects will be destroyed (and their destructors called) as soon as the objects go out of scope.

in oot, we could guarantee that object destructors are called immediately upon going out of scope in certain restricted conditions (we don't want to have to do a complicated escape analysis):

---

alschwalm 11 hours ago [-]

Hy is a great project. One unfortunate thing, though, is that no one has been able to successfully implement `let`, which makes writing idiomatic lisp almost impossible. I'm curious if anyone can actually prove it can't be done or is just very difficult (or too slow to be useful).

reply

aidenn0 7 hours ago [-]

Is there something about Hy's lambda that makes implementing let with it not possible?

For those unaware, the transformation is roughly:

  (let ((foo bar) ...) Y) -> ((lambda (foo ...) Y) bar ...)

[edit]

Reading around it seems that it breaks several pythonic control-transfer functions that treat functions specially (e.g. yield from inside a let would be surprising).

Without knowing much about Hy's semantics, it should still be doable by rewriting variable names. That's likely not doable in a robust manner via a macro though, so would have to be part of Hy's transformation from sexpr to ASTs.

reply

shuzchen 11 hours ago [-]

Hy actually used to have a let (but a broken one) and it was removed in a recent version. For context on this see https://github.com/hylang/hy/issues/1056

reply

((my note: i skimmed that issue, it doesn't really say anything more than that, as other HN comments said, 'let' causes break, continue, yield, async, await to not work, i'm assuming because 'let' covertly calls a function to introduce a new scope. They say that they can't figure out a way to have both. The following blog post confirms this and gives examples:))

tuturto 9 hours ago [-]

I actually wrote a short blog post about this just recently: https://engineersjourney.wordpress.com/2017/08/01/history-of...

In short, let kept running into trouble with yield, exceptions, breaking out of loops and such. It would have been really tricky (probably close to impossible) to get everything working correctly.

reply

bshanks 1 minute ago

parent edit delete on: Hy – A Lisp-flavored Python

In case i ever find myself designing a programming language, what facilities would Python have to support in order to allow 'let' to work? It seems to me that in theory Hy could solve the problem with which variables are to be marked 'nonlocal' by doing a lot of additional code analysis (is this correct?). I gather that the main problem is that break, continue, yield, async, await work differently if you covertly introduce a new function scope. Would it be sufficient if scopes could have explicit labels and break, continue, yield took an argument that said which label to break/continue/yield out of? Would that solve that problem with async, await too? If Hy did extensive code analysis, and if Python's break/continue/yield took this extra argument, would implementation of 'let' by Hy then become possible or are there still additional impediments that i am missing?

tuturto 2017-08-06 at 07:44

Thanks for the reply. Answering took a while as I had to ponder about things in more detail and be extra careful not to commit any big plunders.

Extensive code analysis probably can’t reliable detect when to place nonlocal and when not. Sometimes programmer wants to introduce a new symbol that shadows existing one, sometimes they want to modify value of already existing one that is from outer scope. In order the system to detect what the programmer wants to do in a specific case, it would have to understand semantics of the problem the programmer is trying to solve and how they’re trying to solve it. It would require cognitive capabilities close to human. It really boils down to the fact that while sometimes we would like to have that nonlocal keyword there, knowing what the programmer had in mind when writing partical piece of code is really tricky for computers to do.

Extra labels probably made it at least a little bit easier to deal with the covert function. If we had a situation like this (formatting probably breaks though):

    .(for [x (range 10)]
    …(let [res (+ x 1)]
    …..(if (> x 5)
    …(break)
    (print res)))) 

System would have to detect that break is inside a let and instead of just placing break in the resulting output, generate more sophisticated solution. In theory, it could use return to exit function and signal with special return value that next action should be breaking out of loop. This way the break wouldn’t be inside a function, but outside it, thus in correct scope:

    .for x in range(10):
    …..def _hy_anon_fn_1():
    ……..res = (x + 1)
    ……..if (x > 5):
    …………return “break requested”
    ……..else:
    …………_hy_anon_var_1 = print(res)
    ……..return _hy_anon_var_1
    …._hy_anon_var_2 = _hy_anon_fn_1()
    …..if _hy_anon_var_2 = “break requested”:
    ………break
    …..else:
    ………return _hy_anon_var_2 

And you would have to keep in mind that there might be multiple lets nested each other, each introducing a new function that we first need to escape before issuing break command. I haven’t really looked into what problem we had with async/await, but I imagine it being something similar.

It’s rather hairy problem, with lots and lots of corner cases and I suspect that we didn’t even encounter all of them by the time it was decided that let should be dropped. We really wanted to have let, but it was just too much of trouble for half-working solution that generated many strange and confusing bugs. Reply

nnq 9 hours ago [-]

> but the lack of "let" mostly killed it for me

Can you give an example of code that is improved by let significantly? ...and can't be rewritten in a more readable way without it?

reply

aidenn0 3 hours ago [-]

Any macros that expands to code that will reuse a value. You can't do common, and valuable, constructs like once-only without let.

reply

---

from [2]:

Feature 7: yield from

    Pretty great if you use generators
    Instead of writing
    for i in gen():
        yield i
    Just write
    yield from gen()
    Easily refactor generators into subgenerators.

Feature 8: asyncio

    Uses new coroutine features and saved state of generators to do asynchronous IO.
  1. Taken from Guido's slides from “Tulip: Async I/O for Python 3” by Guido
  2. van Rossum, at LinkedIn?, Mountain View, Jan 23, 2014 @coroutine def fetch(host, port): r,w = yield from open_connection(host,port) w.write(b'GET /HTTP/1.0\r\n\r\n ') while (yield from r.readline()).decode('latin-1').strip(): pass body=yield from r.read() return body
    @coroutine
    def start():
        data = yield from fetch('python.org', 80)
        print(data.decode('utf-8'))

---

Subject: [New comment] History of let

Howdy,

tuturto commented on: History of let.

Comment URL: (https://engineersjourney.wordpress.com/2017/08/01/history-of-let/#comment-303) Post URL: (https://engineersjourney.wordpress.com/2017/08/01/history-of-let/)

This was in response to Eduardo P (@edcrypt):


Functions aren’t the only Python construct introducing some sort of sub-scope, right? I imagine it could be possible to implement a “let” with the “with” statement and an empty context manager wrapper.


I remember context managers being mentioned in regards to “let”, but I couldn’t find any discussion on GitHub? tickets. Maybe it happened in IRC or maybe I misremember. In any case, I gave this some pondering, as I had completely forgotten this avenue of thought. But you’re completely right, “with” statement could be used to create sub-scope.

I think, that it would be possible to build some sort of system using context managers to handle scope, but it would be rather complicated. If I’m not missing something crucial, variable holding the context manager should be named the same as the variable introduced by let. And every time “setv” (or any other function really) tries to access a variable, it should first check if there’s a context manager and use the value store in it instead of a regular variable. This system wouldn’t have problem with “yield”, but I’m thinking it would be rather complicated piece of code and interoperability with Python would be tricky.

---

more on a syntax for state machines

[3] talks about state machines. To specify a state machine, you need:

" Here’s our transition table for the naïve bank account: open held closed open deposit, withdraw place-hold close held remove-hold deposit close closed reopen

In the leftmost column, we have the current state of the account. Each subsequent column is a destination state. At the intersection of the current state and a destination state, we have the event or events that transition the object from current to destination state. Thus, deposit and withdraw transition from open to open, while place-hold transitions the object from open to held. The start state is arbitrarily taken as the first state listed. "

and, sometimes, code to run upon transitions (which is like emitting events)

and, sometimes, code that specifies 'when X event is received, do transition Y' (possibly alongside doing something else? or, we could just duplicate the transition edge; but i suppose some events have to do processing before it is decided which state transition will occur)

Raganwald gives an implementation of a class StateMachine? which executes state machines of the following form:

" const account = StateMachine?({ balance: 0,

  [STARTING_STATE]: 'open',
  [STATES]: {
    open: {
      deposit (amount) { this.balance = this.balance + amount; },
      withdraw (amount) { this.balance = this.balance - amount; },
      placeHold: transitionsTo('held', () => undefined),
      close: transitionsTo('closed', function () {
        if (balance > 0) {
          // ...transfer balance to suspension account
        }
      })
    },
    held: {
      removeHold: transitionsTo('open', () => undefined),
      deposit (amount) { this.balance = this.balance + amount; },
      close: transitionsTo('closed', function () {
        if (balance > 0) {
          // ...transfer balance to suspension account
        }
      })
    },
    closed: {
      reopen: transitionsTo('open', function () {
        // ...restore balance if applicable
      })
    }
  }}); "

so basically we just need to make it possible to write something like that and have a class like Raganwald's StateMachine? consume it; possibly we need a special syntax for writing that sort of thing (but his syntax actually looks pretty good to me)

---

in python "Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement. This is exemplified by the following loop, which searches for prime numbers:

>>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print n, 'equals', x, '*', n/x ... break ... else: ... # loop fell through without finding a factor ... print n, 'is a prime number'

(Yes, this is the correct code. Look closely: the else clause belongs to the for loop, not the if statement.) "

---

[4]

"Blocks are a non-standard extension added by Apple Inc. to Clang's implementations of the C, C++, and Objective-C programming languages that uses a lambda expression-like syntax to create closures within these languages

...

Like function definitions, blocks can take arguments, and declare their own variables internally. Unlike ordinary C function definitions, their value can capture state from their surrounding context. A block definition produces an opaque value which contains both a reference to the code within the block and a snapshot of the current state of local stack variables at the time of its definition. The block may be later invoked in the same manner as a function pointer. The block may be assigned to variables, passed to functions, and otherwise treated like a normal function pointer, although the application programmer (or the API) must mark the block with a special operator (Block_copy) if it's to be used outside the scope in which it was defined.

Given a block value, the code within the block can be executed at any later time by calling it, using the same syntax that would be used for calling a function.

...

A simple example capturing mutable state in the surrounding scope is an integer range iterator:[7]

...

  1. include <stdio.h>
  2. include <Block.h> typedef int (^IntBlock?)();

IntBlock? MakeCounter?(int start, int increment) { __block int i = start;

	return Block_copy( ^(void) {
		int ret = i;
		i += increment;
		return ret;
	});
	}

int main(void) { IntBlock? mycounter = MakeCounter?(5, 2); printf("First call: %d\n", mycounter()); printf("Second call: %d\n", mycounter()); printf("Third call: %d\n", mycounter());

	/* because it was copied, it must also be released */
	Block_release(mycounter);
	
	return 0;}

"

---

" I had some extended notes here about "less-mainstream paradigms" and/or "things I wouldn't even recommend pursuing", but on reflection, I think it's kinda a bummer to draw too much attention to them. So I'll just leave it at a short list: actors, software transactional memory, lazy evaluation, backtracking, memoizing, "graphical" and/or two-dimensional languages, and user-extensible syntax. If someone's considering basing a language on those, I'd .. somewhat warn against it. Not because I didn't want them to work -- heck, I've tried to make a few work quite hard! -- but in practice, the cost:benefit ratio doesn't seem to turn out really well. Or hasn't when I've tried, or in (most) languages I've seen. " [5]

---

want to be able to do something like this Ruby example:

  list_cart unless @cart.include? 'pineapple'

so we need:

---

repeat..until

---

randyrand 2 hours ago [-]

Using #[non_exhaustive] on enums is going to be general bad practice, just as many in c++ consider ‘default’ switch cases bad practice.

When a new state is added to an enum, we want the code to not compile so that we can fix all the places that need updating.

reply

lilyball 1 minute ago [-]

Swift has a really good solution to this, which is an attribute @unknown that you put on the default case, and this attribute produces a warning if there are any known enum variants that would match this case. This way you're future-compatible but the warnings tell you when you need to revisit the code. I'm pretty disappointed that Rust didn't copy this.

reply

Guvante 1 hour ago [-]

That is why there are two versions available. The default matches the behavior you describe and is the default because for small projects it is the right decision.

However sometimes you are a dependency and you want to give up this restriction to gain the ability to add things without having to bump your major version number.

By far the most common example is error enums which don't necessarily need all of their downstream crates to handle every error, they likely are bucketing most of them anyway and non_exhaustive ensures they support that.

reply

kibwen 8 minutes ago [-]

Error enums are precisely the target for this attribute. Servo's URL parser is a great example, as it currently uses a dummy variant that is hidden from the documentation in order to discourage people from trying to exhaustively match over it: https://github.com/servo/rust-url/blob/7d2c9d6ceb3307a3fad4c...

reply

thayne 9 minutes ago [-]

The `Ordering` example given in the announcement shows another use-case. User code typically won't pattern match on that enum anyway, it will usually just be passed as an argument to atomic functions. And it may be desirable to add another type of ordering as rust's memory model evolves.

reply

kibwen 11 minutes ago [-]

This ties into what I believe is going to be one of the biggest themes of programming language development in the 2020s: first-class language features that allows defensive libraries to make changes that don't cause breakage in downstream users. Right now I'd say Swift is the poster child of this movement; many of its language features are head-scratchers until you realize that they exist to keep applications compiling and on a clean upgrade path even when their dependencies are actively changing.

Of course, the trade-off is obviously that by choosing to make things continue to compile when something changes, you are no longer causing things to fail to compile when something changes. I'm uncertain how this tension will be resolved in the long run.

reply

https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html

  1. [non_exhaustive] structs, enums, and variants

Suppose you're a library author of a crate alpha, that has a pub struct Foo. You would like to make alpha::Foo's fields pub as well, but you're not sure whether you might be adding more fields to Foo in future releases. So now you have a dilemma: either you make the fields private, with the drawbacks that follow, or you risk users depending on the exact fields, breaking their code when you add a new one. Rust 1.40.0 introduces a way to break the logjam: #[non_exhaustive].

The attribute #[non_exhaustive], when attached to a struct or the variant of an enum, will prevent code outside of the crate defining it from constructing said struct or variant. To avoid future breakage, other crates are also prevented from exhaustively matching on the fields. The following example illustrates errors in beta which depends on alpha:

---

rwmj 1 day ago [-]

Not a question, a request: Please make __attribute__((cleanup)) or the equivalent feature part of the next C standard.

It's used by a lot of current software in Linux, notably systemd and glib2. It solves a major headache with C error handling elegantly. Most compilers already support it internally (since it's required by C++). It has predictable effects, and no impact on performance when not used. It cannot be implemented without help from the compiler.

reply

rseacord 1 day ago [-]

My idea was to add something like the GoLang? defer statement to C (as a function with some special compiler magic). The following is an example of how such a function could be used to cleanup allocated resources regardless of how a function returned:

  int do_something(void) {
    FILE *file1, *file2;
    object_t *obj;
    file1 = fopen("a_file", "w");
    if (file1 == NULL) {
      return -1;
    }
    defer(fclose, file1);
  
    file2 = fopen("another_file", "w");
    if (file2 == NULL) {
      return -1;
    }
    defer(fclose, file2);
    obj = malloc(sizeof(object_t));
    if (obj == NULL) {
      return -1;
    }
    // Operate on allocated resources
    // Clean up everything
    free(obj);  // this could be deferred too, I suppose, for symmetry 
  
    return 0;
  }

reply

rwmj 1 day ago [-]

Golang gets this wrong. It should be scope-level not function-level (or perhaps there should be two different types, but I have never personally had a need for a function-level cleanup).

Edit: Also please review how attribute cleanup is used by existing C code before jumping into proposals. If something is added to C2x which is inconsistent with what existing code is already doing widely, then it's no help to anyone.

reply

rseacord 1 day ago [-]

Yes, we have discussed adding this feature at scope level. A not entirely serious proposal was to implement it as follows:

  1. define DEFER(a, b, c) \ for (bool _flag = true; _flag; _flag = false) \ for (a; _flag && (b); c, _flag = false)
  int fun() {
     DEFER(FILE *f1 = fopen(...), (NULL != f1), mfclose(f1)) {
       DEFER(FILE *f2 = fopen(...), (NULL != f2), mfclose(f2)) {
         DEFER(FILE *f3 = fopen(...), (NULL != f3), mfclose(f3)) {
             ... do something ...
         }
       }
     }
  }

We are also looking at the attribute cleanup. Sounds like you should be involved in developing this proposal?

reply

a1369209993 1 day ago [-]

Apropos of this, I'll toss in: please support do-after statements (and also let statements).

  do foo(); _After bar();
  /* exactly equivalent to (with gcc ({})s): */
  ({ bar(); foo(); });
  #define DEFER(a, b, c) \
    _Let(a) if(!b) {} else do {c;} _After

(This is in fact a entirely serious proposal, though I don't actually expect it to happen.)

reply

rwmj 1 day ago [-]

Yes, I'll ask around in Red Hat too, see if we can get some help with this.

reply

nrclark 1 day ago [-]

Would it make sense for defer to operate on a scope-block, sort of like an if/do/while/for block instead?

That would allow us to write:

   defer close(file);

or:

   defer {
      release_hardware();
      close(port);
   }

I feel like that syntax fits very nicely with other parts of C, and could even potentially lend itself well to some very subtle/creative uses.

I feel like a very C-like defer would:

reply

eqvinox 1 day ago [-]

Cleanup on function return is not enough, it needs to be scope exit. We're using this for privilege raising/dropping (example posted above) and also mutex acquisition/release. Both of these really "want" it on the scope level.

reply

---

Python-style 'else' blocks on 'for' and 'while' which run only if the loop was not broken, and after try/except, where it runs if there was no exception

---

labeled breaks:

-- https://news.ycombinator.com/item?id=9827437

---

elisp 'when'

---

pass by (?name?) in Iris reminds me of Kernel's f-exprs: " No built-in behavior beyond evaluating values. All other behavior is provided by library-supplied handlers. This includes “standard” behaviors such as assignment and flow control. ... Unlike most imperative languages, command arguments are evaluated by the receiving handler, not at the call site. This allows for deferred evaluation of command arguments, where argument expressions are passed unevaluated and unbound (in a primitive handler), or automatically thunked with the command’s scope before being added to a native handler’s body scope, to be evaluated later only if/when needed. Flow control operations are thus implemented as ordinary handlers; the only difference is in the parameter type, e.g. if {test as boolean, action as expression, alternate_action as expression}. " -- https://github.com/hhas/iris-script

helps with sandboxing:

" e.g. It is trivial to define a JSON-style data-transfer language and safely parse and render it: instantiate an Environment containing no commands or operators except those used to represent true/false/nothing and evaluate code in that. Similarly, the iris-glue code generator imports only a subset of stdlib functionality (coercions) and instead obtains its main behavior from gluelib. " -- https://github.com/hhas/iris-script

---

" Pony while loops are very similar to those in other languages ... But what if the condition evaluates to false the first time we try, then we don’t go round the loop at all? In Pony while expressions can also have an else block. In general, Pony else blocks provide a value when the expression they are attached to doesn’t. A while doesn’t have a value to give if the condition evaluates to false the first time, so the else provides it instead.

So is this like an else block on a while loop in Python? No, this is very different. In Python, the else is run when the while completes. In Pony the else is only run when the expression in the while isn’t.

...

break immediately exits from the innermost loop it’s in. Since the loop has to return a value break can take an expression. This is optional, and if it’s left out, the value from the else block is returned.

...

if no else expression is provided, None is returned. " -- https://tutorial.ponylang.io/expressions/control-structures.html

---

" In rust, blocks are expressions. Eg { a; b } will return b. In zig blocks it's only possible to return values from a block by using a label foo: { a; break :foo b; }. This allows early returns from the block, unlike rust, but I find it harder to read. In rust I sometimes write inner functions to get access to early returns, which is even more verbose.

Overall I prefer the rust approach, but it's a close call. " -- [6]

---