Elektrine lite

← Feed

@DDOtten@mathstodon.xyz

2026-04-24 13:46 UTC

@zyang (cc, @AndrasKovacs) Thank you for the interesting blog post! It really helped me to understand the optimizations that NbE applies. After reading the original blog post I also tried thinking about ways to simplify nf7 and I worked out one alternative (https://github.com/DDOtten/NbE-cached/blob/main/NbE.hs). I implemented a further variation (`nf8` at the end of the file) that avoids the need for the `forceShifts` function. This change is based on storing the number of shifts at the root of the cached normal forms: instead of storing a ~TmS with lazy weakenings we store a ~Tm and the number of shifts that need to be applied to the normal form for this specific occurrence. I hope the computational complexity of `nf8` is the same as `nf7`, since both have to traverse every cached normal form once per occurrence to apply the needed shifts (which depend on the number of additional binders encountered before substituting this occurrence), but I have not worked this out in detail yet. I did compare the output and performance of (both the original and newly updated) `nf7` and `nf8` on the original adversarial example and two new large adversarial examples, and they match, with `nf8` being slightly faster on my computer. However, a more detailed analysis of computational behavior is needed, so please let me know if I made any mistakes!

Replies (1)

  • @zyang@mathstodon.xyz 2026-04-24 15:10

    @DDOtten Thank you so much for the feedback! I am very happy to know you liked the post! I think your nf8 computes the normal form correctly, but I am worrying about the cost when arguments are repetitively reified. When you cache the normal form of an argument, `shift 0 i` is applied to the normal form, but the normal form may become a part of the normal form of another argument, which needs reification and `shift 0` again. In particularly, for the following term ``` -- t = (\x. \y. y x) ((\x. \y. y x) (...)) t :: Int -> Tm t 0 = Abs (Var 0) t n = Abs (Abs (Var 0 `App` Var 1)) `App` t (n-1) ``` where the cached normal form of the argument contains the cached normal form of the argument of the cached normal form of the argument.... `nf8` is slow on my machine: -- >>> fv (nf8 (t 10000)) but `nf5` is fast: -- >>> fv (nf5 (t 10000)) -- -1 (By the way, embarrassingly, the updated nf7 in the current version of my post is in fact incorrect... I am still thinking about if it can be saved...)

    Open ##2127773