
Dependent Types for F# - jackfoxy
https://jackfoxy.github.io/DependentTypes/
======
dwohnitmok
I'm very much a newbie when it comes to F# but as far as I can tell this isn't
an example of what's traditionally called dependent types, but rather is a way
of providing a set of helper functions and types for "smart" constructors,
that is providing a validation/verification function that takes in some raw
type and gives back an option of either the type you want or none.

As a litmus test (and I'd be happy if either the OP or someone else chimes
in), as an example of dependent types, I'd expect to be able to compose
constraints, e.g. having a type called

    
    
        And<'A, 'B>
    

and

    
    
        Or<'A, 'B>
    

that would then let me compose constraints e.g.

    
    
        And<GreaterThan3, IsEven>
    

and crucially _have their verification functions automatically compose as
well_ , instead of having to invent a whole new type

    
    
       GreaterThan3AndIsEven
    

with a whole new verification function.

As far as I can tell right now, that's not possible with this library and I
need to go down the route of making the custom GreaterThan3AndIsEven type.

Another good litmus test is whether literals also need to be passed through
the verification function or not (or whether you have to pass them through the
verification, get an option, and then unwrap the option with a comment
indicating that it's safe to do so); for a usage of dependent types I would
expect the answer to be no usage of the verification function would be
required.

The crucial thing that dependent types let you do is that you can manipulate
types and values with many of the same tools. Composition (the and and or from
above) is one hallmark of this ability. Another is being able to call a value-
level function in a type signature. For example a type signature that looks
like

    
    
        x : if (a + b == c) then Boolean else Int
    

FWIW I don't mean to take away from this library; the practice of using smart
constructors is something I'd love all expressively statically typed languages
embrace wholeheartedly.

~~~
jackfoxy
OP here.

Since Martin-Löf type theory has introduced dependent types through the
dependent function. No one I am aware of has improved on this approach, or
even attempted a different way of introducing dependent types. And I think
theory is very important.

In Practice I think
[https://en.wikipedia.org/wiki/Dependent_type](https://en.wikipedia.org/wiki/Dependent_type)
gets it right in that dependent types exist independently of dependent
functions. _[A] dependent type is a type whose definition depends on a value._
I think ordinary programmers solving ordinary problems benefit far more from
the type safety dependent types provide than dependent functions or compile
time creation checks. In ordinary programming most data is independent of the
code. There are notable exceptions, of course.

If in practical programming we may only ever use a term from computer science
when it meets the CS definition in every way, I invite the reader to start a
list of all the terms where this is already not the case.

Also ordinary programmers are more likely to gain at least a superficial
understanding of dependent types when every implementation of smart
constructors _does not_ call the resulting types something specific to that
programming language, but otherwise unconnected to most anything else.

Constructor composition is a neat idea and merits investigation. The idea of
course springs from function (dependent function) composition. Not exactly
what you are thinking, but I think somewhat related, take a look at the
section _Converting between dependent types_ in the tutorial
[https://jackfoxy.github.io/DependentTypes/tutorial.html](https://jackfoxy.github.io/DependentTypes/tutorial.html)

~~~
dwohnitmok
I agree with I think what you're hinting at with regards to practicality. It
is definitely the case that using this library or the practices that it
espouses is far more practical for the working programmer than to try to get
people to rework rewriting their codebase in Coq.

That being said, if I understand this library correctly, I strongly disagree
with the choice to call it "Dependent Types." There's certainly no inherent
reason that a cabal of type theorists should have a monopoly on the word
"dependently typed," but I think describing this library as dependently typed
dilutes the meaning of this word beyond usefulness. The crucial idea behind
this library can be implemented in every statically typed language I know of
(essentially anything that supports hiding certain constructors and exposing
alternate ones and using some sort of optional type to represent failure
rather than blow up at runtime). The specific implementation details of
storing all the functions together can certainly be approximated in every
statically typed FP language I've used (which is not to say it isn't valuable
or a unique take on the problem!). Under what I think is your definition of
dependent types, Java is a dependently typed language, which really starts
muddying the waters for programmers trying to learn this stuff. This has two
main drawbacks.

1\. It obfuscates what this library is doing to programmers who have not used
dependent types before. The mix of questions in this very HN thread, some of
which are more appropriately directed at a language like Idris, is a testament
to that. Once again depending on whether I understand this library correctly,
this library seems to be an extension of the idea of having separate public
and private constructors for types and codifying the public constructors for a
type. This is something that I would wager most working programming have some
familiarity with and can immediately understand its usefulness instead of
having to ask around (and get irrelevant answers) about what dependent types
are.

2\. It disappoints and confuses people who have used dependent types. Honestly
it comes off as buzzword marketing. An analogy would be if someone took the C
language and added a new word "class" that was a synonym for "struct," did
absolutely nothing else, then proclaimed that C was object-oriented (a term
which is far less well-defined than dependently typed). "Ah so do your new C
OO classes support inheritance?" "Nope." "Hmmm... so do you have methods?"
"Nope." "Well then they aren't really classes and it feels disingenuous to
call them such... (unlike e.g. how C++ extends the semantics of struct)."

If what you're talking about RE Martin-Löf type theory and the dependent
function are pi and sigma types sure that's the formal treatment of dependent
types, but I'd argue that there's a simpler working definition that suffices
for most cases. A dependently typed language is simply a language where
expressions not only can have a type, but can evaluate to a type. Hence my "if
... then ..." example from earlier.

I think the most accurate, widely-used term that describes what's going on
here is the factory pattern. Simple as that and I think that's familiar enough
to most programmers.

Upon re-reading this comment, I want to say that I don't think this library is
trivial (it's not "just" the factory pattern) and that it is definitely a
valuable way of structuring a given pattern! I know putting something on HN is
a great way of getting crowds of know-it-alls to heckle and discourage you so
I hope I haven't done that, just wanted to point out something that was
bothering me.

------
Tehnix
From what I gather, this happens at runtime, which makes me feel it's a bit
unfortunate to call it _dependent_ _types_ , or at least my view of dependent
types is of it being a compile-time guarantee.

Take for example Liquid Haskell[0] where you can, at compile time, make
guarantees about your code. For example, here's a function that makes division
safe, by never allowing the second value, _y_ , to be 0,

    
    
        {-@ safeDiv :: Int -> {v:Int | v != 0} -> Int @-}
        safeDiv     :: Int -> Int -> Int
        safeDiv x y = x `div` y
    

The second annotation is plain Haskell meaning it takes 2 `Int`s and returns
an `Int`, with the third line being the function body. The first _{-@ .. -}_
annotation is the one Liquid Haskell reads, stating it takes an `Int` as the
first argument, another `Int`, which we now refer to as _v_ , that is _not_ 0,
and finally returns an `Int`.

Another practical language with dependent types is Idris[1], which supports
full dependent types with dependent pattern matching.

Finally, for the "why would I use this", I recommend checking out Gabriel
Gonsalez's keynote, from Haskell eXchange 2017, "Scrap your Bounds Checks with
Liquid Haskell"[2]

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

[1] [https://www.idris-lang.org](https://www.idris-lang.org)

[2] [https://skillsmatter.com/skillscasts/10690-keynote-scrap-
you...](https://skillsmatter.com/skillscasts/10690-keynote-scrap-your-bounds-
checks-with-liquid-haskell)

------
lkitching
Does this library actually allow you to express dependent types or is it just
for runtime validation? I can't see any examples in the documentation of a
type depending on a value like the common

    
    
        let v : Vect 3 Int = [1,2,3]
    

example.

There aren't many types shown in the examples but it looks like

    
    
        let myNonEmptyIntSetOpt = [] |> Set.ofList |> NonEmptyIntSet.TryCreate
    

would just return None at runtime rather than a compilation error?

~~~
jackfoxy
How can you do a compile time check on data that has not entered the system?

You can apply the same technique to Vect 3 [1,2,3] or any other type.

~~~
lkitching
How do you express the type `Vect 3 Int` using this library? F# doesn't
support it and all the example types appear to depend on other types, not on
values like 3.

~~~
vorotato
Basically when you take some value the user gives you and cast it to the
dependent type, you can then cover the case where you got some result because
the cast was successful or you got none. You can't have dependent types at
compile time, at least not completely so, though you could use something like
fslint to catch any data being entered in code.

[https://news.ycombinator.com/item?id=7567310](https://news.ycombinator.com/item?id=7567310)

~~~
vorotato
it would seem that in practice you can go even further, essentially writing
exhaustive proofs. However F* already supports this and compiles to F#, so I
would say if you want that level of correctness you should probably use a
language which is better at describing the totality of a function.

------
HumanDrivenDev
Is there a reason dependent types aren't more widely used? Is it just a case
of people not wanting to learn anything new, or are there some show-stopping
issues in practice?

The idea seems promising. If I'm not mistaken it would let you statically
declare that an integer had to be between values X and Y, for example.

~~~
Verdex_3
I think it's two things. The first one is that most people are not comfortable
changing up the programming paradigm that they are most familiar with. They're
familiar with adding some if-statements to verify at run time. They're not
familiar with using the type system to prove the properties of their data.
Proving non-trivial properties can be hard (and in some cases impossible), so
it makes sense that few people are jumping at the opportunity to learn
everything from the ground up all over again when the alternative is to just
use an if-statement.

The second thing is that sometimes you're going to get dynamic data at run
time and you're going to have to use a run time property checker anyways. So
for example if you needed to parse a bunch of text sent in by the user. You
can still use dependent types to offload as much as possible to the type
system, but at some point you'll have to deal with receiving user data. And if
we go back to my first point, people are uncomfortable with using new things.
They won't have a good idea of when to put what into the type system and what
into the run time, so the default will be to just do it all at run time.

Liquid types look kind of promising, but I'm not sure if it's still an active
research area. The stuff rust is doing with affine types is also promising.
It's not dependent, but you can make a bunch of very nifty compile time
checked apis. Finally, ML style types are slowly becoming more familiar in
general in the industry. Once everyone is fully familiar with type parameters,
they'll start to ask about kinds and values in types. However, it may take a
while.

~~~
lomnakkus
> You can still use dependent types to offload as much as possible to the type
> system, but at some point you'll have to deal with receiving user data.

Of course you have do deal with receiving data at run time, but I think very
few people appreciate that it's actually possible to do the "input
verification" at one specific point in your program and then have a "proven"
safe input and then never have to do any validation/verification again. This
even goes for things like "give me a vector of integers between 1 to 3 of size
_exactly_ 9 as input". Of course you still have to _handle_ the invalid cases
in that one specific place, but that's no different from how you'd ideally do
validation anyway. That stuff already _should_ be in a single place, and
dependent types make that utterly obvious at compile time :).

(I think I may actually be agreeing with what you're saying, but I though it
worth expounding on what dependent types can do for you.)

Btw, AFAIUI Liquid Types, at least as far as LiquidHaskell goes, is still a
thing, though it's definitely quite "researchy" and who knows whether it'll
become 'mainstream'.

Liquid Types also seem to be somewhat orthogonal to dependent types since they
usually just rely on an external solver that works by "magic" (SMT) and which
has built-in knowledge of e.g. arithmetic whereas most attempts at dependent
types seem to want to avoid building in any of that knowledge in favor of
induction + a more general "tactics" or "elaboration" type solving where the
programmer guides the solver along. (Idris is an example of the latter, I
think.)

~~~
pron
> it's actually possible to do the "input verification" at one specific point
> in your program and then have a "proven" safe input and then never have to
> do any validation/verification again.

This can be done with "tainting" types, which are simple intersection types
(even Java has them as one of its new pluggable type systems [1]), and doesn't
require dependent types.

[1]: [https://checkerframework.org/manual/#tainting-
checker](https://checkerframework.org/manual/#tainting-checker)

~~~
catnaroek
It's useless if it's optional.

~~~
pron
So is F#.

~~~
catnaroek
Assuming you mean “dependent types in F#” rather than “F#”, after reading the
article, I concluded that these are, in fact, _not_ dependent types, but just
so-called “smart constructors”. Since “dependent types in F#” don't exist, it
doesn't make sense to ask whether they're useful or useless.

~~~
pron
I meant that F# itself is optional. So are tests, by the way, and therefore
also completely useless.

~~~
catnaroek
Yeah, F# is kind of problematic since you aren't forced to use F# to typefully
use F# libraries. Things would be a lot better in a language like Standard ML,
where you can either use a library correctly (assuming it exposes the right
interfaces) or not use it at all. Hooray for abstraction!

------
rob-kuz
Very cool library that Jack implemented here using an idea from my blog. We
are using it in production. Works really nice.

------
jpfed
This is cool, but isn't it more like refinement types?

------
xyz-x
Nice lib and library tests!

------
phillipcarter
This looks neat!

------
Animats
It's like subclassing, but it has to be called something else to be cool.
Objects are so last-cen.

~~~
joeblubaugh
It's significantly different than subclassing. The wikipedia examples are
instructive:

    
    
      a dependent type is a type whose definition depends on a 
      value. A "pair of integers" is a type. A "pair of integers 
      where the second is greater than the first" is a dependent 
      type because of the dependence on the value.

~~~
Animats
That's a subclass with an object invariant.

(Gotta use the new buzzwords to be l33t. Even for old ideas.)

~~~
joeblubaugh
Sure, so if you use Ada you can write types like this and the runtime will
enforce your invariants automatically. What other non-academic languages
support that? It’s something I miss from Ada for sure.

