3 ms·
Author here, there’s one feature I didn’t show in this post that makes things a lot more ergonomic: any expression (most importantly but not limited to function
by brundolf 5y ago
Author here, there’s one feature I didn’t show in this post that makes things a lot more ergonomic: any expression (most importantly but not limited to functions) can have inline const declarations
Example:
func foo(a: number) =>
const b = a * 2,
const c = b + 17,
b * c
Between that and expressive conditionals, I think most ergonomic concerns about a single expression are covered (but I’m happy to deal with any others that come to light!)
As for bifurcating the language: sorta, but there isn’t actually much syntax that’s different between the two contexts. Mostly the use of semicolons, and then the existence of loops in procedures. And all of it on both sides is designed to be familiar to the target audience.
> The fact that I can't take something in a proc block, remove the side effects, and shove it into a func
As a reminder, procs can’t return values at all, so it’s unlikely you’d ever really have a case to do this with a whole chunk of a proc. You might want to extract an expression out of a proc and into a function, but that should work pretty naturally as-is
Edit: and I think actually, the strict policy against return values for procs probably helps with the question of bifurcation in a way. Without it, some people might just prefer to construct their values procedurally. But with this limitation, the whole team has to be on the same page: if you want to re-use logic that derives a value, you have to use a func.
- Spivak 5y agoOooo that's v nice. I guess yeah looping is the only thing that still feels odd. Can you even implement map in Bagel without recursion? And then there's those algos that use in/out sets in a while loop that are probably annoying to express recursively.
- brundolf 5y agoThe assumption in Bagel is that pretty much all looping-like behaviors in a function context will be covered by iterators with chained methods; I’m borrowing (heh) heavily from Rust here (which doesn’t have pure-only contexts, but heavily encourages and supports doing things as expressions). A for-loop is a range, a for-each would just be iterating over an array, building up a single value would be a reduce, etc. There are some more gaps I still need to fill, but it should work pretty nicely for most cases. Though of course, recursion is always still available as a fallback/preference
- NoahTheDuke 5y agoI wonder if a `loop`/`recur` construct like the one Clojure has would help here. Allows for closing over data without having to pass everything into a recursive function, and can handle more complex looping constructs without having to write an explicit "for loop".
- ____________g 5y agoI'm also curious about this func/proc distinction and have some questions. It certainly isn't a new concept. Functional languages like Haskell have a similar distinction between effectful actions and pure functions. D also has pure functions that can only call other pure functions, but I'm not sure how well that has worked out in practice. Languages like SML/OCaml make it easy to write purely functional code, but I don't think the language enforces it. 1: Is there really no way to reuse procedural code in a pure setting? Say I have a procedure proc inPlaceSort(a: mutable IntArray) { ... } Is there any way to use this proc to define a func that returns a sorted copy? For example: func pureSort(a: IntArray): IntArray => mutable array_copy = copyOf(a), sort(array_copy), array_copy Do I have to write two separate sorting algorithms? In Haskell I can implement both efficiently with a single implementation in the ST Monad. 2: Can pure functions perform logging? 3: Can a procedure declare some parameters as immutable or as output-only? - Immutable parameters: often you will want to perform an effect with some context which you don't want to mutate. Const parameters are heavily used in C++ and Rust. - Output parameters: these are parameters that are initialized by the callee and not the caller. Most languages don't need this as they can return one or more values instead, but C# is a language that has this specific feature. 4: Why invent a new term "expressive conditional" when "conditional expression" already exists? :^)
- brundolf 5y ago1. You’re correct, there’s no way to do that. Fortunately in this case, you’d want to use the native sort function anyway because it’ll be much faster than one written in JS/Bagel, and that is accessible in a pure context (as a pure function of an array or iterator; not in-place). There will be some barriers to certain optimizations in Bagel, but that’s done knowingly. Bagel is intended as an application-level language, so certain choices are made that prioritize maintainability over performance. With that said, performance is still a priority, and certain things like the focus on iterators over array-cloning are there to tackle common JS performance problems. 2. I’m planning to make an exception here for debugging purposes; I’m thinking a function or operator that logs a value and then evaluates to it. Not strictly pure, but close enough from a maintainability standpoint. 3. Yes, any type can be labeled “const”, which is recursive. If a proc has a const argument, an otherwise-mutable object can be passed to it and it will just be treated as const within that proc. However, a const object cannot be passed to a non-const parameter (or variable, etc), otherwise some other code could then mutate it. Assignable (output) parameters will not be supported, but contents of parameters can be mutated by procs. I specifically wanted to discourage people from using procs to generate values; they should only say what to do with the values. 4. I may not have been fully versed on the terminology :)