Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Addendum: having `Expression`[0] type representing the expression tree is a really killer feature.

I've been doing a writeup comparing how ORMs work in TypeScript vs ORMs in .NET and one thing that is magical is that having the `Expression` type enables a much more functional and fluent experience with ORMs.

[0] https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...



LINQ in EntityFramework certainly isn't perfect, but frankly it's so far ahead of anything else available in all other languages. It was a brilliant idea to add expressions type into the language AND to create a standard set of interfaces that enable collection interop AND to to then create universal clients for local and remote collections that use all this machinery to deliver first class DX.


"All other languages" is a stretch IMO. Any language that has proper macros (e.g. Rust, Clojure) can do something LINQ-like as well as C#.


Can you post some examples?

Having been working in TS with Prisma for a bit, what stands out is how a Prisma query is effectively trying to express an expression tree in structural form

An example on TS with Prisma:

    const loadedAda2 = await tx.runner.findFirst({
      where: { email: 'ada@example.org' },
      include: {
        races: {
          where: {
            AND: [
              { position: { lte: 10 } },
              { time: { lte: 120 } },
              {
                race: {
                  name: { contains: 'New' }
                }
              }
            ]
          }
        }
      }
    })
(Other query tools like Kysely end up using strings (though with the support of TypeScript at dev time))

And the same in C# with EF and LINQ:

    var loadedAda = await db.Runners
      .Include(r => r.RaceResults.Where(
        finish => finish.Position <= 10
          && finish.Time <= TimeSpan.FromHours(2)
          && finish.Race.Name.Contains("New")
        )
      )
      .FirstAsync(r => r.Email == "ada@example.org");
The difference being that the C# code is actually passing an expression tree and the code itself is not evaluated, allowing C# to read the code and convert it to SQL.


TS does not have macro facilities.

Proper macros operate on AST, which it to say, it is exactly like the Expression stuff in C#, except it can represent the entirety of the language instead of some subset of it (C# doesn't even fully support the entirety of System.Linq.Expressions - e.g. if you want LoopExpression, you have to spell the tree out explicitly itself).


    > TS does not have macro facilities
That I'm aware; I'm curious how this works on Go or Rust.


For details, take a look at the examples here: https://doc.rust-lang.org/book/ch20-06-macros.html

For procedural macros, what you get is not even the syntax tree but rather the token tree: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html. You can then use stdlib to parse that into a Rust syntax tree (which is roughly the equivalent of Expression): https://docs.rs/syn/latest/syn/enum.Expr.html

Or you can do your own parsing, meaning that you can handle pretty much any syntax that can be lexed as Rust tokens. Which means that e.g. the C# LINQ syntactic sugar (from ... select ... where etc) can also be implemented as a macro. Or you could handle raw SQL and do typechecking on it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: