News for hackers. Stuff that matters.
Re:Names are not type safety by identity0 on Sunday November 01, 2020 @10:22PM (#24964562)
I've been thinking for a while about something like an "assertion based type system." Rather than types being variants of other types, types can be described as other types + restrictions. Functions take variables with types but also come with a list of assertions. For example, it is sometimes useful in graphics to differentiate, in the type system, a 3D vector and a normalized vector (e.g. separate Vec3 and Norm3 types) but there is nothing you can do to actually put this into the type system. In some assertion based system, you would write something like: `type Norm3 = Vec3 v given(len(v) == 1)`. You could cast a Vec3 to a Norm3 which would run the assertion at run time, or you could write a function like `normalize` which always returns a Norm3 (maybe even it comes with its own assertion that len(v) != 0). Then you can safely pass your Norm3s to functions that expect normalized vectors. It's always recommended to make bad states unrepresentable, but it's weird how no language has ever given you a system like this.
Re:Names are not type safety by lexi-lambda on Sunday November 01, 2020 @10:39PM (#24964626)
As was already mentioned in another reply, what you are describing are refinement types. A refinement type system actually exists for Haskell: it’s called LiquidHaskell,[1] and though I have not used it for anything serious, it seems to work well for certain kinds of problems.

The main challenge of refinement types is that arbitrary properties are very difficult to check in general. (If that weren’t the case, software verification would be easy!) I wrote about this at length in my previous blog post,[2] and Hillel Wayne wrote about it earlier this year from another perspective.[3]

[1]: https://ucsd-progsys.github.io/liquidhaskell-blog/

[2]: https://lexi-lambda.github.io/blog/2020/08/13/types-as-axiom...

[3]: https://www.hillelwayne.com/post/constructive/

Re:Names are not type safety by one-punch on Monday November 02, 2020 @04:41AM (#24966237)
> arbitrary properties are very difficult to check in general.

To add to this:

In theory, it is not just difficult, but impossible.

Imagine that the refinement predicate is whether the String/Text encode a (non-)halting Turing machine/Haskell program. Checking it would solve the halting problem. And this predicate is the proof of Rice theorem.[1] (Granted, this may require some sufficiently powerful logic such as first order, not sure if this proof applies to LiquidHaskell.)

In practice, I think reasonably intuitive properties are already very difficult to formalize in refinement types.

Re:Names are not type safety by ImprobableTruth on Monday November 02, 2020 @05:36AM (#24966550)
>In practice, I think reasonably intuitive properties are already very difficult to formalize in refinement types.

Could you elaborate on that? Unless you're just saying that formalizing reasonably intuitive properties is difficult in general, I don't see what you mean.

Re:Names are not type safety by ImprobableTruth on Monday November 02, 2020 @04:28AM (#24966154)
>The main challenge of refinement types is that arbitrary properties are very difficult to check in general.

While this is true, I'm not sure how constructive types don't face the very same issue. Ultimately, you will have to provide evidence for the required properties, with constructive types the proof is just (more or less) implicitly embedded into the datatype, which imo just makes dealing with these properties harder rather than easier.

In regards to the post by Hillel Wayne you've posted, I'm not sure what this statement

>This means that predicative data is easier to express at the cost of permitting representable invalid data. This is enough of a problem that we prefer constructive data whenever feasible.

is supposed to mean exactly. If your predicate makes some data invalid, it's just not representable in the refined type.

Re:Names are not type safety by hwayne on Monday November 02, 2020 @11:46AM (#24970066)
> is supposed to mean exactly. If your predicate makes some data invalid, it's just not representable in the refined type.

Author here. It's representable; the type checker has to prove it never happens in practice. This requires very different techniques from typechecking constructive types. Liquid Haskell shells out to Z3, which can return "unknown" as an answer in addition to "sat" or "unsat".

(There's actually a fourth option, which is that Z3 runs forever, but I think LH does a bunch of clever design to avoid that case.)

You can also `assume` that refinements are satisfied without proving them, in which case they can be violated at runtime. This would be impossible if the invalid data was unrepresentable.

Other approaches to refinement types, such as the original ML paper [0], implemented them constructively and didn't have this problem.

[0]: https://www.cs.cmu.edu/~fp/papers/pldi91.pdf

Re:Names are not type safety by one-punch on Monday November 02, 2020 @04:51AM (#24966296)
> While this is true, I'm not sure how constructive types don't face the very same issue.

Not sure if the author claims that constructive types do not face this issue.

On constructive types vs refinement types, there is a recent Reddit discussion on this topic,[1] prompted by Facebook’s use of Dependent Haskell to eliminate bugs.[2]

[1]: https://www.reddit.com/r/haskell/comments/jh7575/eliminating...

[2]: https://www.youtube.com/watch?v=10gSoVZ5yXY

Re:Names are not type safety by ImprobableTruth on Monday November 02, 2020 @05:39AM (#24966574)
>Not sure if the author claims that constructive types do not face this issue.

This might be true, but when they were making the case for constructive types they didn't mention it.

>On constructive types vs refinement types, there is a recent Reddit discussion on this topic

The discussions seems to be mostly dependent types vs refinement types, which I'd argue is a false dichotomy. I'd say that dependent types is a what makes refinement types actually powerful in the first place. It's also how most Coq specifications are written.

Re:Names are not type safety by one-punch on Monday November 02, 2020 @01:19PM (#24971216)
> This might be true, but when they were making the case for constructive types they didn't mention it.

Agree.

> The discussions seems to be mostly dependent types vs refinement types, which I'd argue is a false dichotomy.

You are right, I think I confused two notions:

1. constructive vs non-constructive reasoning.

2. intrinsic vs extrinsic types (aka Church vs Curry types[ 1]).

In general, intrinsic types are constructive (because Program-is-Proof due to Brouwer–Heyting–Kolmogorov and Curry–Howard and others).

But extrinsic types could also be constructive, esp. when the external static analyzer has access to the program, such as in refinement reflection [2] (as pointed out in the above Reddit comment thread).

[1]: https://lispcast.com/church-vs-curry-types/

[2]: https://ucsd-progsys.github.io/liquidhaskell-blog/2016/09/18...

Then I think the author is arguing for constructive proofs, which come naturally with intrinsic types, or at least with extrinsic analyzers accessing program structures; and arguing against non-constructive (extrinsic) types, or anything detached from implementation.

Re:Names are not type safety by kbr on Sunday November 01, 2020 @10:26PM (#24964577)
I think you might be describing refinement type systems [1]?

[1] https://en.wikipedia.org/wiki/Refinement_type

Re:Names are not type safety by kylereeve on Monday November 02, 2020 @10:29AM (#24969108)
I've been reading about that, can't seem to wrap my head around the difference between refinement and dependent types.
Re:Names are not type safety by kbr on Monday November 02, 2020 @02:08PM (#24971806)
I think dependent types are stronger than refinement types because they can encode types that don't necessarily have to be decidable. Refinement types are used for creating subsets of a type, while dependent types can be used for creating arbitrary types based on values. As a result, dependent types are more powerful, but they might be too much if all you need are some constraints on an existing type.
Re:Names are not type safety by seanwilson on Sunday November 01, 2020 @10:27PM (#24964580)
Sounds like you're describing dependent types. You've now got the problem that showing your program is correct involves writing maths proofs that show your properties hold in all cases (which is an undecidable problem).
Re:Names are not type safety by nine_k on Sunday November 01, 2020 @10:51PM (#24964685)
But maybe analyzing all cases is not needed if we know (or postulate) something about the predicates? E.g. we can prove (or just assume since the case is simple) that construction of Norm3 is defined for any triple except (0, 0, 0), and that it is preserved under rotation and under reflection, but guaranteed to be broken under addition.

I suspect that if we factor out such basics, the amount of proof for a reasonable function may become manageable.

Re:Names are not type safety by seanwilson on Sunday November 01, 2020 @11:11PM (#24964764)
> or just assume since the case is simple

That's like what the blog post is about where you're tagging values you assume have a certain property.

> I suspect that if we factor out such basics, the amount of proof for a reasonable function may become manageable.

It's not obvious how you contain the complexity though and you don't need a lot before it becomes well beyond practical for even experienced programmers. The moment you have to write an algebraic proof where variables are multiplied together you're in the realm of nonlinear arithmetic which is undecidable for example, and writing proofs is hard.

Mainstream languages contain the proof complexity by limiting what you can express e.g. it's trivial for the computer to prove "String = String" and "Dictionary = HashTable" as part of type checking.

Re:Names are not type safety by brundolf on Monday November 02, 2020 @12:19AM (#24965030)
This is similar to the philosophy behind Clojure's "spec" system: https://clojure.org/about/spec

It's mainly concerned with runtime-checking, but the idea is to establish a standard that can be leveraged in multiple ways, from tests to (I believe) some degree of static analysis

Re:Names are not type safety by sankha93 on Sunday November 01, 2020 @10:37PM (#24964617)
What you are looking for are refinement type systems. LiquidHaskell [0] is the most well known refinement type system out there, to specify and verify these kind of assertions.

[0]: https://ucsd-progsys.github.io/liquidhaskell-blog/