frankpf | 3 years ago | on: Photoprism – open-source Google Photos Alternative
frankpf's comments
frankpf | 4 years ago | on: Structured Concurrency
[1]: https://www.javaadvent.com/2020/12/project-loom-and-structur...
frankpf | 5 years ago | on: An Object-Oriented Language for the '20s
frankpf | 5 years ago | on: Efficient Integer Overflow Checking in LLVM (2016)
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
"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
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
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 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
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.htmlfrankpf | 5 years ago | on: What the Hell Is a Deno?
> 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?
> - 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
frankpf | 5 years ago | on: Janet: a lightweight, expressive and modern Lisp
(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: JITs are un-ergonomic
frankpf | 6 years ago | on: Dynamic type systems are not inherently more open
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....
frankpf | 6 years ago | on: Parcel – Fast, zero-configuration web application bundler
To put it another way, would you willingly download and execute 730 programs from unknown authors on your computer?
frankpf | 6 years ago | on: Parcel – Fast, zero-configuration web application bundler
frankpf | 6 years ago | on: Simpler UI Logic With Finite State Machines
https://gist.github.com/frankpf/cde7f792580f731dfe886ed2d91b... (see the second file for usage)
frankpf | 6 years ago | on: TypeScript 3.7: The Biggest Features and How to Use Them
- 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.
frankpf | 6 years ago | on: ORMs Are Backwards
Here's your query in knex, a query builder for JS:
knex.insert({ bar: 'bar', baz: 'baz' }).into('foo')