frankpf's comments

frankpf | 5 years ago | on: Efficient Integer Overflow Checking in LLVM (2016)

> - abstract interpretation using a streamlined version of the octagon domain.

Do you have any links/source code where I can read up about this (possibly in the context of compilers)? I've never heard of the term octagon domain before.

frankpf | 5 years ago | on: The social and economic costs borne by young people without offices

I think it's been observed by many psychology studies that conscientiousness (the personality trait that determines self discipline and self control) tends to increase with age. Excerpt from https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2562318/:

"Agreeableness demonstrated a fairly linear increase with age whereas the pattern for Conscientiousness was curvilinear: scores increased up to a peak somewhere between the ages of 50 to 70 and then declined."

frankpf | 5 years ago | on: Prologue: Full-Stack Web Framework Written in Nim

I don't think you can use _refinement types_ for this, at least not as of now (e.g. you can't do {.requires validEmail(email).}), but you can use distinct types[1] in nim to model what you want:

    type Email = distinct string
Now Email is a type that is incompatible with string, even though its runtime representation is a string (i.e. no additional overhead). To convert a string to an Email, you can call Email("a"). In real-world code, you would call that constructor only in a few "safe" functions:

   # I'm raising an exception, but you could use an option or a result type
   proc validateEmail(str: string): Email =
      if email.contains("@"):
          return Email(str)
      raise newException(InvalidEmail, "not valid")
Then, in your client code you can have:

    proc createUser(email: Email, password: Password) =
       ...
And the type system guarantees that you only call createUser with "Email"s, not plain strings. Of course, you have to be dilligent not to use the Email constructor directly and instead use the validateEmail function to create "Email"s.

[1]: https://nim-lang.org/docs/manual.html#types-distinct-type

EDIT: The sibling comment mentioned io-ts.

The pattern for doing this in io-ts (and TypeScript in general) is very similar to Nim, although they call it "branding"[2] as it abuses the structural type system to simulate nominal typing.

[2]: https://michalzalecki.com/nominal-typing-in-typescript/#appr...

frankpf | 5 years ago | on: Prologue: Full-Stack Web Framework Written in Nim

> Looks more like flow typing than refinement types to me.

I'm not super familiar with the theory behind refinement types and I could be wrong :)

My familiarity with refinement types comes from things like Liquid Haskell and F*. If you look at Liquid Haskell's manual[1], their definition of refinement types seems to pretty much match what DrNim is offering here.

I've never seen the term flow typing used outside of TypeScript-style analysis, where that means narrowing types like `string | null` to `string` inside an `if (x != null)` block.

> The (static) type of 'y' changes seemingly implicitly in the body of the 'if'. It's an 'int' at assignment but needs to be an 'int {.requires ??? > 0.}' (or however the one would write that type literal in Nim), and some flow typing determines than that that cast form 'int' to 'int {.requires ??? > 0.}' is safe in the body of the 'if', and performs this cast implicitly.

I think the hard part is that the language must keep track of these implicit types (e.g. ranges of values an integer can be) inter-procedurally, i.e. throughout the whole call graph.

But yes, the type does change implicitly, and I don't think `int {.requires ? > 0.}` can be expressed as a type in Nim. But I was also under the impression that you can't express that "explicitly" in e.g. Liquid Haskell (I could be completely wrong).

> It's also not dependent typing as the constraint on 'b' can't be an arbitrary function. (I guess, please correct me if I'm wrong; but in that case this typing wouldn't be decidable, even with the help of SMT).

I think you're right. From my understanding, dependent types are more powerful than refinement types but also more cumbersome to use, refinement types can use SMT solvers and are generally more approachable IMO. There's a nice discussion here[2].

[1]; https://ucsd-progsys.github.io/liquidhaskell-tutorial/01-int... (see section 1.2) [2]: https://www.reddit.com/r/dependent_types/comments/ay7d86/wha...

frankpf | 5 years ago | on: Prologue: Full-Stack Web Framework Written in Nim

I'm in the process of rewriting the language from typescript to nim, so right now it's a recursive descent parser written in typescript[1]. Typescript generates the textual bytecode and that feeds into a Nim interpreter.[2]

I think I'll keep the recursive descent parser even after rewriting to Nim. It's easy to change the grammar and understand how it works, and I can have good error messages & handling.

IMO the main problem is that right now the code for parsing infix expressions is fairly repetitive, I'd like to eventually use Pratt parsing for that.

[1]: https://github.com/frankpf/kiwi/blob/vm/src/parser.ts [2]: https://github.com/frankpf/kiwi/blob/vm/src2/src/interpreter...

frankpf | 5 years ago | on: Prologue: Full-Stack Web Framework Written in Nim

I've been writing an interpreter with Nim and overall the language is really good.

One feature that I'm excited for is DrNim[1]. It's not distributed with the stable version just yet (you have to compile it yourself, but that's very straightforward), but it's supposed to bring refinement types to Nim. These allow you to write things like:

    proc safeDivide(a: int, b: int): float {.requires b > 0.} =
        a / b
and DrNim will check at compile-time that all code paths lead to `b` being greater than 0, e.g.:

    var x = stdin.readLine.parseInt
    safeDivide(1, x) # DrNim will error at compile-time 
    var y = stdin.readLine.parseInt
    if y > 20:
       # no error since DrNim (using Z3) can
       # prove that y is always greater than 0 here
       safeDivide(1, y)
[1]: https://nim-lang.org/docs/drnim.html

frankpf | 5 years ago | on: What the Hell Is a Deno?

Your original point was:

> you could not have different versions sitting side-by-side.

bundler can't do that either. You can't depend on both rails 5 and rails 6 in a single package. Most languages can't do that.

> Any project that's lived long enough runs into some sort of version mis-match where the solution is `rm -rf node_modules`.

I agree, but that's not the only solution, as I've said you could write something similar to require "bundler/setup" in JS that does version checking.

> The version of ruby and the version of the gem are explicit allowing separation.

You can specify the node version in your package.json.

EDIT: on the version checking point, I agree that this is a deficiency of npm. It probably should ship something similar to bundler/setup by default and encourage users to do

   require('npm/validatePackageVersions') # or import 'npm/...' in es6
in their top level code.

I was just pointing out that this is not a fundamental limitation of npm, and it should be fairly easy to implement in user-level code

frankpf | 5 years ago | on: What the Hell Is a Deno?

> - Yarn helped solve that, but because of its backwards compatibility to node_modules, you could not have different versions sitting side-by-side.

> - Node_modules could have a different version installed vs lock file and no one would know without looking.

> Sadly, Ruby's Bundler has solved this for years [...]

I don't understand your first point. Different projects can use different versions since the modules are installed locally (inside the `node_modules` directory). And nested modules can also have different dependency versions, e.g.:

    A
    => depends on B @ 1.0
    => depends on C, and C can depend on B @ 2.0
Regarding your second point, I haven't ever seen that happen in practice and IIUC it's mostly a property of the the fact that `require 'bundler/setup'` checks your dependency versions, and you could implement something similar for JS (e.g. traverse node_modules directories recursively checking if the versions declared in the package.json of dependencies match the ones in your root lockfile).

Since we're on the topic of Ruby and JS, Ruby's module system is probably one of the worst I've ever seen and JS one of the best.

In Ruby, just like in Python, everything in a file is public by default and the only way to make things private, AFAIK, is using Module#private_constant, and that only works for methods/inner classes/things in a class scope.

And, unlike Python's import, require is side-effectful! If you have file a.rb that requires b.rb, and b.rb requires c.rb, everything in c.rb will be visible in a.rb. This is terrible.

JS's module system is one of the best IMO (better than haskell, python, java, etc):

- simple mental model: a file is a module

- everything in a module is private by default, you have to explicitly mark things with `export` to make them public

- You can either qualify imports or explicitly import individual functions, so it's always possible to find out where something is defined by simply looking at a file. Most languages fail here. This is useful for beginners and in places where you don't have an IDE available, like GitHub

frankpf | 5 years ago | on: Janet: a lightweight, expressive and modern Lisp

I like that too, but both Rust and Ruby allow multiple expressions/statements before the last expression. In Haskell and OCaml IIUC you need to use things like `let x = ... in <expr>` or `<expr> where x = ...`, so you still have a single expression in function bodies.

frankpf | 5 years ago | on: Janet: a lightweight, expressive and modern Lisp

I don't use lisp languages, but one basic difference this has from other lisps is that functions accept multiple expressions, e.g.:

    (defn greet [firstname lastname]
      (def fullname (string firstname " " lastname))
      (string "Hello, " fullname))
In other lisps, you'd have the last expression nested inside a `let` block:

    (defn greet [firstname lastname]
      (let [fullname (string firstname " " lastname)]
        (string "Hello, " fullname)))
which makes the code hard to read and edit IMO. Languages like Haskell and OCaml suffer from a similar problem too.

frankpf | 6 years ago | on: Dynamic type systems are not inherently more open

> There hasn't been a lot of study on this topic* but what little there is shows that 3% of errors found can be mitigated with type systems, where they do not exist, fixing these classes of errors takes less time than it took to use the type system.

It seems to me like you're cherry picking evidence. Copy-pasting from a previous comment I made in another thread (https://news.ycombinator.com/item?id=19530274):

There is plenty of evidence showing that modern type systems reduce bugs considerably.

In Airbnb, they found out that 38% (!) of bugs could have been prevented by using TypeScript[1].

Another scientific study discovered that TypeScript and Flow could prevent about 15% of bugs in committed code [2]. And these aren't even measuring reduction of bugs in non-committed code!

Stripe is also writing their own type checker for Ruby and engineers have reported an increase in productivity[3].

[1]: https://news.ycombinator.com/item?id=19131272

[2]: https://blog.acolyer.org/2017/09/19/to-type-or-not-to-type-q....

[3]: https://sorbet.run/talks/StrangeLoop2018/#/

frankpf | 6 years ago | on: Parcel – Fast, zero-configuration web application bundler

Those dependencies, many probably written by unknown authors with 0 stars on github, have complete access to your computer and can execute arbitrary code. Even though it shouldn't affect production, that's still a big problem for your own machine.

To put it another way, would you willingly download and execute 730 programs from unknown authors on your computer?

frankpf | 6 years ago | on: TypeScript 3.7: The Biggest Features and How to Use Them

Not related to the new assert signatures feature (which is great!), but IMO the two best approaches to do what you want today are:

- io-ts[1]

This requires you to write your types as a runtime value, and allows you to extract static types from those, e.g.:

    const ContactInfo = t.type({
       address_1: t.string,
       ...
    })

    type ContactInfo = t.TypeOf<typeof ContactInfo>
You can validate objects with `ContactInfo.decode(someObject)`

Note that we can have the same name for the type and value because they live in different namespaces. When you do ContactInfo.decode, you're calling the decode property of `const ContactInfo`. When you use `ContactInfo` in a type position (e.g. `function x(arg: ContactInfo)`), you're using the `type ContactInfo` declaration

- typescript-is[2]

This uses TypeScript's transformer API. You can use it with https://github.com/cevek/ttypescript. It generates validators for your interfaces/types/etc at compile-time.

[1]: https://github.com/gcanti/io-ts

[2]: https://github.com/woutervh-/typescript-is

frankpf | 6 years ago | on: ORMs Are Backwards

It's not a dichotomy between raw SQL and ORMs.

Here's your query in knex, a query builder for JS:

    knex.insert({ bar: 'bar', baz: 'baz' }).into('foo')
page 1