Just this morning, when I was biking to work, I was thinking about this. For many decades I have felt that object-oriented has some flaws and that value oriented has more promises. You could consider transactions applied to databases as functions applied to a value. Some of the reasoning in the article, is about isolation. And the idea of pointers has some resemblems with. Whether two transaction influence each other (can be swapped) depend on whether the function that represent those transaction do commute.
But still, I think that the proposed solution is at the wrong level. I think it would be much better to talk about the specification versus the implementation level. Instead of saying this function works correctly when certain pre-conditions are met, you could also say that a function implements a function under for a certain value domain. The article, the function 'floorSqrt' could be viewed as a function, which implements the generic floor of square root function, for a certain value domain, namely positive 'int' values. (You wonder whether the input type was not of 'unsigned int'.) It is not even clear from the context, what is the maximum value of 'int'. That the result is always valid for the input values is clear.
I think that for the development of software engineering, it would be better to focus less on 'programming' languages and more on 'implementation' languages, languages that specify how a generic (often matchematical) problem, can be implemented in a programming language. Instead of developing yet another programming language.
One such "implementation language" as you call it is data-oriented programming (not to be confused with data-oriented design, which is used in game development and sort of has the same idea but different). DOP is build around the idea that your data object are just hashmaps and that you don't translate them to "real" objects. Like a record from a database or the response of a REST API is just a hashmap. Then you just have a transformations between these objects. The idea sort of sounds horrible because you are told not to do this but when done "the right way" it is a very flexible way to organise application. I think typescript can help with its opt-in approach to typing. Clojure has also explored much of the same idea and has optimised data structures for this. Yehonathan Sharvit has given some interesting talks about this and published a book about it[1][2].
Anyway I thought it was sort of ironic that the idea is this good because it does the thing everyone tells you not to do (hashmap all the things), but now that someone has written a book on it you have at least the air of respectability.
It's unfortunate that this idea shares a name with the somewhat unrelated Data Oriented Programming (focused on performance and cache utilization).
That said, I very much believe in the value of values. Data should have no associated behavior or have a "place" in memory, the place and the behaviour are secondary. That's why, ironically, "Manager" objects are the best type of object you can have, contrary to OO best practice, because they don't own the data, they just provide a place and behaviour for a particular usage.
The idea of "everything is a hash-map" is also possible in statically typed languages to some extent using row-polymorphic structural types. OCaml and Elm or Koka are great examples.
Yes! I've been playing with this recently. For context, long term, I'd like a pushout lattice of theories (types, operators, laws) to get math-like locality. But for a more near-term puzzle, what might highly expressive metaprogramming and collaborative compilation look like?
There are lots of papers on say "tweak some language to add a bit more control over memory layout"... but what if you want subsume all such, concisely. To permit much more gracefully moving around implementation design space. So you can flexibly and concisely express and mixin domain "asks" - this range of values, that variant of `mod`, this handling of alu flags, "try it exact, now bounded exact, now floatNN, now run UW's Herbie to optimize float expressions", with what radix and offsets and scaling, with what intermediates, with what bits landing where manged by whom, etc. Etc - that was merely numbers. With sort of Maude-like[1] "in this scope I want imported these operators and semantics", but more composably over a higher-dimensional design space.
Apropos a sibling comment, I'm currently at compile/collaboration-time "hashmap all the things", tied in with scoping. Eg, it's not just a literal "5" ast node, it's a rich set of collaboratively and lexically expressed and inferred constraints and preferences over implementations. (If anyone knows of a rich hashmap/assoc/dict combinator library, I'd appreciate hearing of it.)
Great article, and I agree that Mutable Value semantics are an underused sweet spot in the semantic space. I’ve been watching Val from afar with great interest.
I do think however that (safe) Rust can be said to have mutable value semantics, if you forget what a borrow “is” you find yourself exactly with a value-oriented language.
There’s something really satisfying about having a working program, and then unlocking totally new functionality by just placing a sneaky goto, though.
I recently had to use a goto for the first time in years.
Structured programming is fine, but almost no languages except python support the while...else construct.
The semantics are to execute a loop body while the condition is true, and then to execute the else body when it isn't. But if you break out of the while loop, the else is not executed.
Yes, that is another way, but it involves setting a value and another test for it. This is in the context of an extremely fast algorithm and the overhead here is not acceptable.
It does not always hurt to use goto to implement the correct control flow when required.Arguably, the code is clearer as well.
EDIT: I'd prefer to actually have a while else construct though!
I'm not sure we need a whole new programming paradigm and languages (with the hiccup that they'll require bindings to other FFI-libs that do not have as much guarantees). If you have a rich domain and want to benefit from performance boost using "fenced" mutations. Haskell has linear-types, software-transactional memory, and the ST monad. Which are three ways to constrain side-effects over references. If you need tight control of "the CPU as a state-machine", for instance, for IO-heavy services, then you have Rust, ADA and other C-targeting DSLs
I've been working on multithreaded code that passes references around. If you hold the reference, you have exclusive access for reading and writing the data at the reference. In theory you should only send values between threads when you need to pass exclusive access to a thread. At all other times, data is completely sharded between threads. The only reason to pass a reference around is to pass data between threads, similar to a queue but not quite. I deliberately avoid updating shared data structure such as a list that both threads are processing. Instead, each thread gets a shard.
I get 6,359,793 requests per second of passing reference around with this approach. Ideally each reference should correspond to a large amount of CPU work (at least 1 second), so that you shouldn't need to pass around references between threads that fast. This is 150 nanoseconds for a synchronization event, roughly 1.5 memory accesses.
Value orientation reminds me of the old language SETL, where the basic values were immutable sets. It was possible to compile SETL-like programs to efficient code with some interesting representations.
I just posted on r/ProgrammingLanguages yesterday asking for examples of languages or papers like this! Nice.
I’m working on a language that will be sort of a cleaner TypeScript, more simple and sound, with powerful static types, but not ML.
I think functions taking not just read-only views of data structures but deeply “const,” unchanging views is really important. Code that runs in GCed runtimes like JS engines or the JVM can be quite performant, but not with the level of copying and allocation that immutable data structures typically require (when you need to model them changing over time), and I want to solve that at the type level.
19 comments
[ 2.7 ms ] story [ 71.0 ms ] threadBut still, I think that the proposed solution is at the wrong level. I think it would be much better to talk about the specification versus the implementation level. Instead of saying this function works correctly when certain pre-conditions are met, you could also say that a function implements a function under for a certain value domain. The article, the function 'floorSqrt' could be viewed as a function, which implements the generic floor of square root function, for a certain value domain, namely positive 'int' values. (You wonder whether the input type was not of 'unsigned int'.) It is not even clear from the context, what is the maximum value of 'int'. That the result is always valid for the input values is clear.
I think that for the development of software engineering, it would be better to focus less on 'programming' languages and more on 'implementation' languages, languages that specify how a generic (often matchematical) problem, can be implemented in a programming language. Instead of developing yet another programming language.
Anyway I thought it was sort of ironic that the idea is this good because it does the thing everyone tells you not to do (hashmap all the things), but now that someone has written a book on it you have at least the air of respectability.
[1] https://www.youtube.com/watch?v=Vz9rkr_p3Ts [2] https://www.manning.com/books/data-oriented-programming
That said, I very much believe in the value of values. Data should have no associated behavior or have a "place" in memory, the place and the behaviour are secondary. That's why, ironically, "Manager" objects are the best type of object you can have, contrary to OO best practice, because they don't own the data, they just provide a place and behaviour for a particular usage.
The idea of "everything is a hash-map" is also possible in statically typed languages to some extent using row-polymorphic structural types. OCaml and Elm or Koka are great examples.
Yes! I've been playing with this recently. For context, long term, I'd like a pushout lattice of theories (types, operators, laws) to get math-like locality. But for a more near-term puzzle, what might highly expressive metaprogramming and collaborative compilation look like?
There are lots of papers on say "tweak some language to add a bit more control over memory layout"... but what if you want subsume all such, concisely. To permit much more gracefully moving around implementation design space. So you can flexibly and concisely express and mixin domain "asks" - this range of values, that variant of `mod`, this handling of alu flags, "try it exact, now bounded exact, now floatNN, now run UW's Herbie to optimize float expressions", with what radix and offsets and scaling, with what intermediates, with what bits landing where manged by whom, etc. Etc - that was merely numbers. With sort of Maude-like[1] "in this scope I want imported these operators and semantics", but more composably over a higher-dimensional design space.
Apropos a sibling comment, I'm currently at compile/collaboration-time "hashmap all the things", tied in with scoping. Eg, it's not just a literal "5" ast node, it's a rich set of collaboratively and lexically expressed and inferred constraints and preferences over implementations. (If anyone knows of a rich hashmap/assoc/dict combinator library, I'd appreciate hearing of it.)
[1] err, not a great sample but: https://maude.lcc.uma.es/maude321-manual-html/maude-manualch...
I do think however that (safe) Rust can be said to have mutable value semantics, if you forget what a borrow “is” you find yourself exactly with a value-oriented language.
And I think it's a wonderful way to code once you wrap your head around it and unlearn Java.
https://en.wikipedia.org/wiki/Software_transactional_memory
The best experience that i've used is Haskell, where there's no "goto".
Structured programming is fine, but almost no languages except python support the while...else construct.
The semantics are to execute a loop body while the condition is true, and then to execute the else body when it isn't. But if you break out of the while loop, the else is not executed.
It does not always hurt to use goto to implement the correct control flow when required.Arguably, the code is clearer as well.
EDIT: I'd prefer to actually have a while else construct though!
I read a whitepaper on value semantics: https://arxiv.org/pdf/2106.12678.pdf
I think it's promising.
I've been working on multithreaded code that passes references around. If you hold the reference, you have exclusive access for reading and writing the data at the reference. In theory you should only send values between threads when you need to pass exclusive access to a thread. At all other times, data is completely sharded between threads. The only reason to pass a reference around is to pass data between threads, similar to a queue but not quite. I deliberately avoid updating shared data structure such as a list that both threads are processing. Instead, each thread gets a shard.
I get 6,359,793 requests per second of passing reference around with this approach. Ideally each reference should correspond to a large amount of CPU work (at least 1 second), so that you shouldn't need to pass around references between threads that fast. This is 150 nanoseconds for a synchronization event, roughly 1.5 memory accesses.
https://cs.nyu.edu/faculty/paige/courses/setl/course.html
http://www.cs.ox.ac.uk/jeremy.gibbons/wg21/paige.html
I’m working on a language that will be sort of a cleaner TypeScript, more simple and sound, with powerful static types, but not ML.
I think functions taking not just read-only views of data structures but deeply “const,” unchanging views is really important. Code that runs in GCed runtimes like JS engines or the JVM can be quite performant, but not with the level of copying and allocation that immutable data structures typically require (when you need to model them changing over time), and I want to solve that at the type level.