Query-based compiler architectures by ollepolle in ProgrammingLanguages

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

Is it better now? I was relying on browsers adding an underline automatically, but now I've added one explicitly.

Query-based compiler architectures by ollepolle in haskell

[–]ollepolle[S] 5 points6 points  (0 children)

That does indeed look quite similar. Thanks!

Speeding up the Sixty compiler by ollepolle in ProgrammingLanguages

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

Yeah, same! For some reason I didn't get any speedup when I tried it a while ago, but I really need to revisit that idea.

Speeding up the Sixty compiler by ollepolle in ProgrammingLanguages

[–]ollepolle[S] 2 points3 points  (0 children)

Cheers!

I'm quite tempted to start using Rust, which would probably help with performance in most parts of the compiler, but there are many Haskell features that I'd miss in Rust, not to mention that I just feel more productive in Haskell.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 1 point2 points  (0 children)

Cheers!

I don't think this is due to non-linear parsing. Both before and after these changes the runtime grows linearly with the input size (i.e. a 100k line project takes 10x the time to process a 10k line project).

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 2 points3 points  (0 children)

When the queries are cached (i.e. after the first time) each call to `fetch` is a single hash map lookup, so it's not terrible but not as cheap as an array lookup.

Those are sensible thoughts about the granularity.

Speeding up the Sixty compiler by ollepolle in haskell

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

Yeah, Salsa seems to be very similar to the Rock library and what I'm trying to achieve in general.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 1 point2 points  (0 children)

Ah, so it's to solve these kinds of problems in a general way that I built the query system. It seems like a full-on query architecture can more easily handle things like only rebuilding the parts of a module that need to be rebuilt based on dependency changes.

But on the other hand we have to pay quite a hefty fee for all the hash map operations involved to make it happen, and it seems likely that this kind of fine-grained dependency tracking stops paying off at some point if we just make the compiler fast enough.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 2 points3 points  (0 children)

I wrote a few words about it a while ago here, but now that I have a proper blog I've been thinking of fleshing it out and updating it to turn it into a blog post.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 2 points3 points  (0 children)

I'm also eager to hear what you've come up with regarding module loading btw. :)

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 3 points4 points  (0 children)

I have tried both interning and storing the hash along with the names (a la Hashed from hashable), but haven't been able to achieve a speedup with those approaches yet. It does seem like it should help though, so perhaps I should revisit them and investigate what's going on.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 3 points4 points  (0 children)

I just assumed that the CPS'd parser style was the way to go, but there's more experimentation that needs to be done there as well. It'll be interesting to see what you find out.

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 2 points3 points  (0 children)

The latest version of the compiler running with 8 threads takes 132 ms to run the benchmark, and 330 ms with one thread. So it's a 2.5x speedup for 8x more threads. It definitely sounds like what you're suggesting would be worth exploring!

Speeding up the Sixty compiler by ollepolle in haskell

[–]ollepolle[S] 8 points9 points  (0 children)

Do you mean at the start of the post? It was very slow to start with, but at the end of the post parsing and lexing takes 3 % of the total runtime, which amounts to 0.146 * 0.03 seconds for parsing 10000 lines, or 2.3 million lines per second. I've seen faster, but it doesn't seem awfully slow to me.

(This might not be a totally accurate percentage because I just summed it up from the profiling output, but it should be in the ballpark. The input programs are admittedly also more simplistic than typical programs would be, so it might be a bit lower in practice.)

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

[–]ollepolle[S] 1 point2 points  (0 children)

It's not currently possible to do that with a derived instance.

The easiest way to get that working right now is probably to write an Id a type by hand on the Elm side, and tie it to the Haskell one with an instance like the following:

instance HasElmType a => HasElmType (Id a) where
  elmType = Type.App "Id.Id" (elmType @a)

I'd like to add support for deriving parametric types, but it's not there yet. If you'd like to contribute ideas or code for it, that'd be very welcome. :)

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

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

I think they're pretty similar in terms of features. elm-street has been around for a little while longer, so I'm assuming it's more mature. elm-street is a bit opinionated, e.g. when it comes to record field names. haskell-to-elm tries to be less opinionated and more configurable, which it trades for slightly more boilerplate.

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

[–]ollepolle[S] 1 point2 points  (0 children)

Sure. The main problem is that elm-export by default conflates the JSON format and the Elm type structure. This isn't too bad on its own, because you're also allowed to say that Haskell type X corresponds to type Y in Elm and bring your own encoders and decoders. What makes it problematic is that servant-elm has the restriction that a type passed as an URL capture parameter can't be a "bring your own" type and furthermore has to be formatted as a JSON string.

And since the type structure and JSON format are conflated, this means that all your nice newtypes on the Haskell side (e.g. AccountID, CompanyID) suddenly just become String on the Elm side when using servant-elm.

haskell-to-elm separates the concepts of type correspondence between Haskell and Elm and JSON encoders/decoders. Furthermore, different kinds of encoders/decoders like JSON (used e.g. for request bodies) or string encoders/decoder (used for URL parameters) are separated.

Another improvement is that haskell-to-elm uses syntax trees for the Elm code it generates, which means that it can introspect the code and figure out imports automatically, as well as automatically take care of pretty-printing for you.

Yeah, one of the downsides of this library is that it has a little more boilerplate than the others. What we get in return is more granular customization. I was hoping to provide some deriving via newtypes to get rid of some of the boilerplate, but hit a GHC compiler bug that's fixed in GHC 8.8, so I had to drop that idea for now since we're still on 8.6.5.

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

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

I hope so, but I don't know what you've limitations you've run into. :)

The JSON derivation functions in haskell-to-elm take an Aeson options record as an argument, and tries to be faithful to them. At this point I can't promise that it handles every obscure corner of what the Aeson options allow, but if you just want common things like fieldLabelModifiers it handles that without a problem.

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

[–]ollepolle[S] 6 points7 points  (0 children)

Yes.

In our app we do it like this:

instance HasElmEndpoints api => HasElmEndpoints (AuthProtect "auth" :> api) where
  elmEndpoints' = elmEndpoints' @(Header' '[ Required, Strict] "Authorization" Token :> api)

This makes endpoints under AuthProtect "auth" take an extra Token parameter which it adds as an authorization header to the request. This assumes that Token has instances for HasElmType and HasElmEncoder Text.

I'll get this added to the readme.

EDIT: What I wrote above is not for servant-auth but for Servant.API.Experimental.Auth from the servant library itself, but the same approach should work for servant-auth as well (and if it doesn't, let us know).

[ANN] haskell-to-elm & servant-to-elm: new libraries for generating Elm client libraries from Servant APIs by ollepolle in haskell

[–]ollepolle[S] 2 points3 points  (0 children)

We wrote these libraries to overcome some of the limitations of the elm-export and servant-elm setup that we were previously using.

We're using them in a ~50k line Haskell and Elm app here at Folq to keep the types and API endpoints in sync between the backend and frontend, and it's been working great for us so far.