5 ms·
Sort of - I'm specifically avoiding/getting rid of result types. Usually with null-coalescing ? you'd go "optional ?. optional ?. field" => optional. But becaus
by FeepingCreature 3y ago
Sort of - I'm specifically avoiding/getting rid of result types. Usually with null-coalescing ? you'd go "optional ?. optional ?. field" => optional. But because '?' acts as a control flow primitive (via `breakelse`), it's actually "optional? .optional? .field" => field, ie. each optionality is done away with by branching away, and so no longer represented in the type system at all.
If you have `Optional<Foo>`, and you access `foo?.field`, then the type is not `Optional<Field>`, but just `Field` - because in the None case, you've already exited the expression. So it's not `(foo)?.(field)`, but `(foo?).field`. To steal a phrasing from another comment, the non-local flow of `?` effectively everts the Optional foo.
- camgunz 3y ago(I'm hoping my tone is getting this across but, I like geeking out about PLs so I'm going whole hog haha. I like this language a lot FWIW; feels pretty advanced and very thoughtful :) I like result types, but I do think they're clunky because you have to handle them in a generic way (pattern matching) or add some (incongruous, IMO [0]) syntax sugar to handle them. Modeling them w/ a keyword and some syntax avoids that weird layering violation so I can support this. I guess my only beef here is giving any access to "breakelse" at all. Like, if you need the concept in the compiler OK, but I'm worried that outside the need to support "?", "breakelse" is a mental model annihilation machine. A better name would be "justkidding" or "sike" haha. > Sort of - I'm specifically avoiding/getting rid of result types. Whaaaaaa? I think you still have them because you wrote: > That is, if would see that there was a possibility of an :else return type, and use this opportunity to jump to the else block instead of declaring a variable. Or in the "eatLine" example, "eatLine" has to return Result<string> because you don't know if it'll always be called with "?". That's the ":else return type". You either need some kind of--very narrow--checked exceptions or whatever to enforce "?", or code has to know about the :else return type. OK with that in mind, I now think you're talking about exceptions because of the type conundrum. There's another example someone posted about their language and a "while" loop (which is a pretty cool idea). After working with it a bit in this context, I didn't find it really any different than a try, e.g.: try { string line := eatLine() string[] words := split(line) int[] swear_words := get_swear_word_indices(words)) ... catch { ... } Maybe there's an argument here that like, all Neat is doing is some implicit Result typing and exceptions are a world away, but I think they're closer than they seem because the behavior you're aiming at is the essence of exceptions: short-circuit on error. I will admit there's a significant difference between what you've implemented and the rest of exceptions (unwinding, sending metadata through a side channel, etc.) but I think a lot of people find that full exceptions are very useful in the general case and naked "catch" satisfies the YOLO subset case. Plus, bubbling enables you to quarantine the result types to non-error code. [0]: "?" has to know there's a Result type, and which fields are the good/bad fields--it's odd for an operator to know anything about complex, generic types defined at compile time, let alone rely on their semantics.
- FeepingCreature 3y ago> (I'm hoping my tone is getting this across but, I like geeking out about PLs so I'm going whole hog haha. I like this language a lot FWIW; feels pretty advanced and very thoughtful :) Hooray! :) > Whaaaaaa? I think you still have them because you wrote: Yeah, but keep in mind that this feature is "in development". I'm increasingly just not even using the pattern-matching "pull out :else" property of `if` at all, and while functions return Result types, they're nearly always immediately done away with in some form. I can't think of an occasion in the compiler (my biggest project!) where I have a function returning an optional value and holding on to it as optional. And yep, it's pretty equivalent to exceptions. The behavior flows like an implicit try/catch. The difference is mostly that exceptions pull in a lot of expensive compiler machinery, whereas breakelse is really just a local branch - and if you tried to ignore it, you'd get type errors, whereas exceptions (unless you have checked exceptions) "fail silent" at compiletime. So the idea of :else return values is that the function is telling you: "There's also a chance I'm not giving you a return value, but something else; you should probably handle this case somehow." I could make operators that propagate this info, but I think it's valuable right where it is, ie. directly after the function call. And there you should either place a marker (?) indicating that you're trusting the compiler to get rid of this state in some way: either via if branch or via a local `else` block, as in the common `foo()? else die`, or explicitly get rid of it in some other way. But going forward from `?`, the matter should be handled; we should avoid carrying it with us any farther than we have to. So in that way it is very exception-like. > Sort of - I'm specifically avoiding/getting rid of result types. So yeah, that's what I mean with that. I do have result types in the language, but the point is that I don't propagate them but try to dissolve them locally. The appropriate reaction to an operation yielding a missing optional value should usually be to abandon the current scope right there and then, in some way, not to "will they won't they" with an optional type for another five steps before deciding that "actually, I don't want to do anything with this because it is indeed missing." It's broadly equivalent to typical result/monad based idioms in code, I just think it's a different way of thinking about it that's more suited for the C-like view.
- camgunz 3y agoYeah that's the other thing: Results melt away more or less so it's kind of a big todo about nothing. Promises I understand passing around but like, feels like a lot of machinery (generics, sum types, pattern matching) just to let a function say "whoops". This feels like a good compromise to me actually. It doesn't pull in all the baggage of other possible implementations/mental models (exceptions, result types, blah), it's basically just a step up from tuple types, like reifying a given return value as an error that needs to be handled, which is probably at least the 80% case. Pretty cool.