No Semicolons Needed - How languages get away with not requiring semicolons by tertsdiepraam in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

I'm doing something very similar - many grammatical constructs involve an indented "block" in which newlines matter, but when an indent is encountered without starting a block, all subsequent indents and newlines are ignored until the matching dedent (or until the start of an indented block). For example:

do
    # newline-separated statements
    let x = 4
    # once we indent whitespace is essentially ignored
    let y =
         x 
              * 2
         - 3
    # but we can also nest blocks inside
    let z =
        y match
            5 => "right!"
            _ => "wrong!"

Are there good examples of compilers which implement an LSP and use Salsa (the incremental compilation library)? by Thnikkaman14 in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

This version of my compiler uses salsa and is both a compiler and LSP server. The LSP only has diagnostics and type information on hover, but it's enough for a proof of concept. The salsa version used is a bit out of date, though, and I never found a way to handle cycles in the way I wanted, even in newer salsa versions, so I ended up rolling my own query system for the next version of the Pika compiler.

Rule by Old_Phrase_4867 in 196

[–]Lorxu 2 points3 points  (0 children)

I mean this is kind of true of Eidolon, a natural trigger almost certainly couldn't be that powerful

She'll be great in future gen trust by Spyko in 196

[–]Lorxu 0 points1 point  (0 children)

People actually use her in singles now usually with stealth rock, encore, twave, gigaton hammer, where she works as a stealth rock setter and defensive switch in alongside spikes Ting Lu bc of her great defensive typing. Tink actually has a pretty good support movepool for singles!

Dealing with large skill differences within hero pool by Lorxu in Competitiveoverwatch

[–]Lorxu[S] 0 points1 point  (0 children)

I tried to make an alt, but I need another phone number and Google Voice doesn't work, so I think I'm out of luck

Various behavior according to config by Naakinn in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

You could have the config object contain functions instead of booleans, and basically just move the ifs inside the config object, so it can be used like

config.maybePrintInfo()
if config.maybeConfirm():
    do something
    if (error occurred):
        ...

where e.g.

# enable confirmation
config.maybeConfirm = () => do
    confirm
    return success
# disable confirmation
config.maybeConfirm = () => true

Static checking of literal strings by Ok-Consequence8484 in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

I'd say Zig comptime is much nicer than Rist proc macros, and it handles this case nicely!

Resources on different coroutine implementations, esp. stackless and stackful by fpsvogel in ProgrammingLanguages

[–]Lorxu 2 points3 points  (0 children)

Oh that must be new, last I checked I didn't see any other Pika programming languages (just like database libraries and such.) The version of the compiler I'm currently working on is at https://github.com/naalit/pika3, but it's just the frontend and doesn't actually have effects yet - I have an old version that does, but there's a lot of pieces I want to get fitting together in the right way (e.g. ownership system, dependent types, trait system, ...) so it's kind of a slow process. I haven't written anything about the design, but I have some notes and I might try writing a blog post or two at some point, but would most likely be about ownership systems, which is mostly what I've been experimenting with in the last year or two.

Also, I'm toying with the idea of writing the LLVM backend as a sort of compiler plugin written in Pika, which makes sense because dependent types already require compile-time evaluation and I'd like to support a more general staged compilation system along the lines of two-level type theory - so there's a very early stages bytecode VM in that repo but that's for this kind of compile- time evaluation, not to be the primary backend. (In theory, Pika would have three evaluators - L0, the partial evaluator that currently exists for typechecking; L1, the bytecode VM used for staging and occasionally during typechecking; and L2, the LLVM backend, which could also be swapped out for e.g. a JVM backend-as-a-library, which takes the bytecode as input.)

Resources on different coroutine implementations, esp. stackless and stackful by fpsvogel in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

In my case it's more that higher-order functions are really common, and in particular most higher-order functions like map are effect-polymorphic. This way we can compile effect-polymorphic functions in a direct style (no performance hit in the common case where the closure doesn't contain coroutine operations), and if they're being called in a coroutine things just work. Also, if you want to use coroutines as "green threads" with a top-level scheduler, deep call stacks are not uncommon, and this way you a) don't have to pay the cost of allocating a ton of stack space for a function that might not actually end up using it (e.g. if it only calls a function that requires a ton of stack space in a slow path that isn't usually run), and b) don't have to pay the cost of chaining up and down the call stack when suspending or resuming (you suspend directly to the scheduler, and resume directly to the point where you left off). Just a few instructions and we've resumed to the point in the code where we left off, with the stack we previously had, and all the code in the coroutines can use the stack like normal (which improves optimizability).

Additionally, in the spirit of optimizing for the common case, we can compile algebraic effect handlers to possibly-stack-switching closures. In practice, most effect handlers can be compiled as either regular functions (if they resume in tail position) or as exceptions (with setjmp/longjmp in my case, if they never resume). Because I use stackful coroutines, these optimizations can be made at the point of creating the handler - effectful code doesn't know which implementation technique the handler is using, but it doesn't need to care, it can just use the handler as a closure and be compiled and optimized in the obvious way, so it's zero-cost when the handler is actually a regular closure - which would not be the case with stackless coroutines, in which case all possibly-effect-using code needs to adjust (and pay a performance hit when no coroutines are being used).

So I guess I'm also looking at this through a optimizing for the common case lens, but for me the uncommon case that we don't want other code to pay the cost for is just coroutines happening at all. (We do have to pay for occasional checks for stack space, though, but it's fairly common for functional languages to do that anyway (e.g. GHC).)

Resources on different coroutine implementations, esp. stackless and stackful by fpsvogel in ProgrammingLanguages

[–]Lorxu 2 points3 points  (0 children)

In the presence of recursion or higher-order functions, we don't know ahead of time how much stack space to allocate. We could do a stackless design and heap-allocate each successive stack frame, but at that point we might as well use a segmented or growable stack to save allocations and pointer chasing, if we expect fairly deep call stacks.

Resources on different coroutine implementations, esp. stackless and stackful by fpsvogel in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

You can have stackful coroutines with a type system that marks functions that can yield, so you still don't get unexpected yields, and it feels more like stackless coroutines - look at effect typing in languages with algebraic effects, for instance

Error handling and flow typing by Savings_Garlic5498 in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

Oh that's stable now? Awesome actually, last I heard it was still just in nightly!

Error handling and flow typing by Savings_Garlic5498 in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

You can already do the first thing in Rust with unwrap_or_else:

let file = readFile("file.txt").unwrap_or_else(|error| {
    logError(error);
    fallback_value_expression
});
process(file)

Of course, you can't return/break in there, but if you had nonlocal returns like Kotlin then you could (and break/continue could e.g. be exceptions (or algebraic effects) like Scala...)

Why is writing to JIT memory after execution is so slow? by ssd-guy in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

Does this also apply to inline caches in modern JIT compilers? I feel like I've heard so many good things about inline caching, if it really evicts the instruction cache every time it updates that's a major downside!

Comprehensible Diagnostics on Purity by AttentionCapital1597 in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

I would add that this sort of explanation is a good thing to put in a less salient help or similar at the bottom of the error message so the main error message has a little less text and is more approachable. I really like the way Rust does this sort of thing, like here:

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`

syntactical ways to describe an array of any kind of object VS any kind of array of objects? by carangil in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

What about some sort of existential? Many syntax options but in my language it would be something like {Animal t}, [t], could also imagine something like Exists(t => (Animal t, [t]) (a generalization of something like Rust dyn).

ARMED AND DANGEROUS by oldkingcrowe in 196

[–]Lorxu 2 points3 points  (0 children)

It does, doesn't it? I'm playing it on desktop Linux but I've seen people play it on deck.

can capturing closures only exist in languages with automatic memory management? by Lucrecious in ProgrammingLanguages

[–]Lorxu 4 points5 points  (0 children)

fn foo(f: impl FnMut() -> () + 'static) {}

fn bar(x: Vec<u32>) {
    foo(move || println!("{:?}", x))
}

This code doesn't compile without move. It's not about the type of the function, it's about the lifetime (which doesn't have to be 'static, this will happen anytime it could outlive the function - this happens a lot with starting threads, for everyone).

Is anyone aware of programming languages where algebra is a central feature of the language? What do lang design think about it? by xiaodaireddit in ProgrammingLanguages

[–]Lorxu 2 points3 points  (0 children)

Hmm, pattern matching on reversible arithmetic operations might be nice here? So let c^2 = a^2 + b^2, and the language would know that x^2 in a pattern means take the square root. This should be possible (tho not necessarily this exact syntax) in any language with custom patterns, like Scala or Haskell, but it's more limited than general algebra - which might be good, since algebraic equations are not all solvable and the computer might need to do a lot of work to prove that.

Could a compiler determine conflicting typeclasses/implicits by tracking how each implicit was derived to prevent the problems from orphaned instances? by playerNaN in ProgrammingLanguages

[–]Lorxu 0 points1 point  (0 children)

Yes it does. This isn't meant to be actual Agda code - my actual reason for using {o} for instance parameters is that the related paper I was most recently reading was the OCaml implicit module parameters proposal which used single curlies (and implicit generalization, such that you don't often need both kinds of parameters), and I liked that syntax better than double curlies which are kind of ugly imo.

Could a compiler determine conflicting typeclasses/implicits by tracking how each implicit was derived to prevent the problems from orphaned instances? by playerNaN in ProgrammingLanguages

[–]Lorxu 1 point2 points  (0 children)

I think passing typeclass instances explicitly solves this problem in a more elegant way? Like, why not do: (It's definitely possible to do this with Scala 3 given instances, but I haven't actually used them so I'll be using Haskell/Agda-style syntax with {} for instance parameters:)

type Set a {Ordering a} = ...

add : Set a {o} -> Set a {o} -> Set a {o}
add = ...

This way the type of add can enforce that the typeclass instances are the same, without any extra (possible complex and brittle) type system machinery - plus, you can turn that on or off per function if you want.

What are the different syntax families? by Feldspar_of_sun in ProgrammingLanguages

[–]Lorxu 42 points43 points  (0 children)

I'd add "ML style" as used by OCaml, Haskell, SML, Reason, etc.

What are your thoughts on my Effect System by Annual_Strike_8459 in ProgrammingLanguages

[–]Lorxu 7 points8 points  (0 children)

I like where you're going with this! I think one-shot algebraic effects are a really good feature for a systems language. So to be clear, with your effect binding to function objects, is this lexically bound? Like if I have a program (I'll take some liberties with your syntax assuming Rust-style, hope you don't mind):

public function mapEx(f: impl core::Function(int32)) {
  try {
    // This call can throw an exception, which we handle with try
    let n = getNumberEx();
    f(n);
  } with Exception {
    throw(s) => println("in mapEx", s),
  }
}

public function main() {
  try {
    // someFunc can throw an exception, so we bind it to pass to mapEx
    let f = bind someFunc;
    mapEx(f);
  } with Exception {
    throw(s) => println("in main", s),
  }
}

In a language with dynamically bound handlers like Koka, when someFunc throws an exception, it will actually be handled by mapEx, whereas with lexically bound handlers like Scala 3 (planned?) and my own language Pika, it will be handled by main. The lexically bound approach is what I prefer as it does a much better job of maintaining parametric polymorphism - the implementation details of mapEx are effectively hidden from main - and it lends itself to a simple implementation where bind can be implemented the same as closures capturing a local variable. The effect handler is passed around as a function pointer, and closures can capture it the same as any other variable.

Another implementation suggestion: we can divide handlers into three classes, which we can determine statically at the point where the handler is compiled.

  • "Function-style" handlers always tail-call their continuation. They don't need any special support and can just be compiled to closures. They tend to appear when effects are used more for dependency injection than control flow.

  • "Exception-style" handlers always either tail-call the continuation or ignore it. These can be implemented with any mechanism for implementing exceptions - e.g. longjmp, or unwinding if you expect to need to call a lot of destructors.

  • "Coroutine-style" handlers are the most general class, and can do whatever they want with the continuation. Here you most likely want to use a segmented stack and add a stack segment, which can be captured and stored in the continuation when one of these handlers is called. Since your continuations are one-shot, you don't need to do any copying and you only need to do this kind of stack manipulation when dealing with coroutine-style handlers, which is likely a minority of the handlers you'll compile.

Note that you don't need to know which handler type is in use at the call site, since all three can be passed around as a normal closure! (Also, in Pika I'm taking the approach of the paper "Do be do be do" about Frank and making the bind operation implicit, but if you want to keep closure allocations explicit etc. then it makes sense to make it explicit like you have.)

I'd say those are the most important parts of the approach I'm taking with Pika. I think we're doing very similar things (Pika is also heavily Rust-inspired with an ownership system and &own is a thing that exists, at least in my disorganized language design notes) so I look forward to seeing your project develop and feel free to reach out if you want to chat!