A theorem prover / proof assistant supporting (multiple) subtyping / subclassing [closed] - haskell

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 8 years ago.
Improve this question
In short, I am looking for a theorem prover which its underlying logic supports multiple subtyping / subclassing mechanism.( I tried to use Isabelle, but it does not seem to provide a first class support for subtyping. see this )
I would like to define a couple of types among which some are subclasses / subtypes of others. Furthermore, each type might be subtype of more than one type. For example:
Type A
Type B
Type C
Type E
Type F
C is subtype of A
C is also subtype of B
E and F are subtypes of B
PS:
I am editing this question again to be more specific (because of a complains about being of-topic!): I am looking for a theorem prover / proof assistance in which I can define the above structure in a straight forward manner (not with workarounds as it is kindly described by some respectable answers here). If I take the types as classes then It seems above subtypings could be easily formulated in C++! So I am looking for a formal system / tool that I can define such a subtyping structure there and I can reason?
Many thanks

PVS has traditionally emphasized "predicate subtyping" a lot, but the system is a bit old-fashioned these days and has fallen behind the other big players that are more active: Coq, Isabelle/HOL, Agda, other HOLs, ACL2.
You did not make your application clear. I reckon that any of the big systems could be applied to the problem, one way or the other. Formalization is a matter to phrase your problem in a suitable way within the given logical environment. Logics are not programming languages, but have the real power of mathematics. Thus with some experience in a particular logic, you will be able to do great and amazing things that you did not expect at first sight.
When choosing your system, lists of particular low-level features are not so relevant. It is more important that you like the general style and culture of the system, before you make a commitment. You can compare that to learning a foreign language. Before you spend months or years to study do you collect features of the grammar? I don't think so.

You include the 'isabelle' tag, and it happens to be that according to "Wiki Subtyping", Isabelle provides one form of subtyping, "coercive subtyping", though as explained by Andreas Lochbihler, Isabelle doesn't really have subtyping like what you're wanting (and others want, too).
However, you're talking in vague generalities, so I easily provide a contrived example that meets the requirements of your 5 types. And though it is contrived, it's not meaningless, as I explain below.
(*The 5 types.*)
datatype tA = tA_con int rat real
type_synonym tB = "(nat * int)"
type_synonym tC = int
type_synonym tE = rat
type_synonym tF = real
(*The small amount of code required to automatically coerce from tC to tB.*)
fun coerce_C_to_B :: "tC => tB" where "coerce_C_to_B i = (0, i)"
declare [[coercion coerce_C_to_B]]
(*I can use type tC anywhere I can use type tB.*)
term "(2::tC)::tB"
value "(2::tC)::tB"
In the above example, it can be seen that types tC, tE, and tF lend themselves naturally, and easily, to being coerced to types tA or tB.
This coercion of types is done quite a bit in Isabelle. For example, the type nat is used to define int, and int is used to define rat. Consequently, nat is automatically coerced to int, though int isn't to rat.
Wrap I (you haven't been using canonical HOL):
In your previous question examples, you've been using typedecl to introduce new types, and that doesn't generally reflect how people define new types.
Types defined with typedecl are nearly always foundational and axiomatized, such as with ind, in Nat.thy.
See here: isabelle.in.tum.de/repos/isabelle/file/8f4a332500e4/src/HOL/Nat.thy#l21
The keyword datatype_new is one of the primary, automagical ways to define new types in Isabelle/HOL.
Part of the power of datatype_new (and datatype) is its use to define recursive types, and its use with fun, for example with pattern matching.
In comparison to other proof assistants, I assume that the new abilities of datatype_new is not trivial. For example, a distinguishing feature between types and ZFC sets has been that ZFC sets can be nested arbitrarily deep. Now, with datatype_new, a type of countable or finite set can be defined that can be nested arbitrarily deep.
You can use standard types, such as tuples, lists, and records to define new types, which can then be used with coercions, as shown in my example above.
Wrap II (but, yes, that would be nice):
I could have continued with the list above, but I separate from that list two other keywords to define new types, typedef and quotient_type.
I separate these two because, now, we enter into the realm of your complaint, that the logic of Isabelle/HOL doesn't make it easy, many times, to define a type/subtype relationship.
Knowing nothing much, I do know now that I should only use typedef as a last resort. It's actually used quite a bit in the HOL sources, but then, the developers then have to do a lot of work to make a type defined with it easy to use, such as with fset
http://isabelle.in.tum.de/repos/isabelle/file/8f4a332500e4/src/HOL/Library/FSet.thy
Wrap III (however, none are perfect in this imperfect world):
You listed the 3 proof assistants that probably have the largest market share, Coq, Isabelle, and Agda.
With proof assistants, we define your priorities, do our research, and then pick one, but it's like with programming languages. We're not going to get everything with any of them.
For myself, mathematical syntax and structured proofs are very important. Isabelle seems to be sufficiently powerful, so I choose it. It's not a perfect world, for sure.
Wrap IV (Haskell, Isablle, and type classes):
Isabelle, in fact, does have a very powerful form of subclassing, "type classes".
Well, it is powerful, but it is also limited in that you can only use one type variable when defining a type class.
If you look at Groups.thy, you'll see the introduction of class after class after class, to create a hierarchy of classes.
isabelle.in.tum.de/repos/isabelle/file/8f4a332500e4/src/HOL/Groups.thy
You also included the 'haskell' tag. The functional programming attributes of Isabelle/HOL, with its datatype and type classes, help tie the use of Isabelle/HOL to the use of Haskell, as demonstrated by the ability of the Isabelle code generator to produce Haskell code.

There are ways to achieve that in agda.
Group the functions related to one "type" into fields of record
Construct instances of such record for the types you want
Pass that record along into proofs that require them
For example:
record Monoid (A : Set) : Set where
constructor monoid
field
z : A
m+ : A -> A -> A
xz : (x : A) -> m+ x z == x
zx : (x : A) -> m+ z x == x
assoc : (x : A) -> (y : A) -> (z : A) -> m+ (m+ x y) z == m+ x (m+ y z)
open Monoid public
Now list-is-monoid = monoid Nil (++) lemma-append-nil lemma-nil-append lemma-append-assoc instantiates (proves) that a List is a Monoid (given the the proofs of Nil being a neutral element, and a proof of associativity).

Related

What are algebraic structures in functional programming?

I've been doing some light reading on functional programming concepts and ideas. So far, so good, I've read about three main concepts: algebraic structures, type classes, and algebraic data types. I have a fairly good understanding of what algebraic data types are. I think sum types and product types are fairly straightforward. For example, I can imagine creating an algebraic data type like a Card type which is a product type consisting of two enum types, Suit (with four values and symbols) and Rank (with 13 values and symbols).
However, I'm still hung up on trying to understand precisely what algebraic structures and type classes are. I just have a surface-level picture in my head but can't quite completely wrap my head around, for instance, the different types of algebraic structures like functors, monoids, monads, etc. How exactly are these different? How can they be used in a programming setting? How are type classes different from regular classes? Can anyone at least point me in the direction of a good book on abstract algebra and functional programming? Someone recommended I learn Haskell but do I really need to learn Haskell in order to understand functional programming?
"algebraic structure" is a concept that goes well beyond programming, it belongs to mathematics.
Imagine the unfathomably deep sea of all possible mathematical objects. Numbers of every stripe (the naturals, the reals, p-adic numbers...) are there, but also things like sequences of letters, graphs, trees, symmetries of geometrical figures, and all well-defined transformations and mappings between them. And much else.
We can try to "throw a net" into this sea and retain only some of those entities, by specifying conditions. Like "collections of things, for which there is an operation that combines two of those things into a third thing of the same type, and for which the operation is associative". We can give those conditions their own name, like, say, "semigroup". (Because we are talking about highly abstract stuff, choosing a descriptive name is difficult.)
That leaves out many inhabitants of the mathematical "sea", but the description still fits a lot of them! Many collections of things are semigroups. The natural numbers with the multiplication operation for example, but also non-empty lists of letters with concatenation, or the symmetries of a square with composition.
You can expand your description with extra conditions. Like "a semigroup, and there's also an element such that combining it with any other element gives the other element, unchanged". That restricts the number of mathematical entities that fit the description, because you are demanding more of them. Some valid semigroups will lack that "neutral element". But a lot of mathematical entities will still satisfy the expanded description. If you aren't careful, you can declare conditions so restrictive that no possible mathematical entity can actually fit them! At other times, you can be so precise that only one entity fits them.
Working purely with these descriptions of mathematical entities, using only the general properties we require of them, we can obtain unexpected results about them, non-obvious at first sight, results that will apply to all entities which fit the description. Think of these discoveries as the mathematical equivalent of "code reuse". For example, if we know that some collection of things is a semigroup, then we can calculate exponentials using binary exponentiation instead of tediously combining a thing with itself n times. But that only works because of the associative property of the semigroup operation.
You’ve asked quite a few questions here, but I can try to answer them as best I can:
… different types of algebraic structures like functors, monoids, monads, etc. How exactly are these different? How can they be used in a programming setting?
This is a very common question when learning Haskell. I won’t write yet another answer here — and a complete answer is fairly long anyway — but a simple Google search gives some very good answers: e.g. I can recommend 1 2 3
How are type classes different from regular classes?
(By ‘regular classes’ I assume you mean classes as found in OOP.)
This is another common question. Basically, the two have almost nothing in common except the name. A class in OOP is a combination of fields and methods. Classes are used by creating instances of that class; each instance can store data in its fields, and manipulate that data using its methods.
By contrast, a type class is simply a collection of functions (often also called methods, though there’s pretty much no connection). You can declare an instance of a type class for a data type (again, no connection) by redefining each method of the class for that type, after which you may use the methods with that type. For instance, the Eq class looks like this:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
And you can define an instance of that class for, say, Bool, by implementing each function:
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False
p /= q = not (p == q)
Can anyone at least point me in the direction of a good book on abstract algebra and functional programming?
I must admit that I can’t help with this (and it’s off-topic for Stack Overflow anyway).
Someone recommended I learn Haskell but do I really need to learn Haskell in order to understand functional programming?
No, you don’t — you can learn functional programming from any functional language, including Lisp (particularly Scheme dialects), OCaml, F#, Elm, Scala etc. Haskell happens to be a particularly ‘pure’ functional programming language, and I would recommend it as well, but if you just want to learn and understand functional programming then any one of those will do.

Type constraints on dimensionality of vectors in F# and Haskell (Dependent Types)

I'm new to F# and Haskell and am implementing a project in order to determine which language I would prefer to devote more time to.
I have a numerous situations where I expect a given numerical type to have given dimensions based on parameters given to a top-level function (ie, at runtime). For example, in this F# snippet, I have
type DataStreamItem = LinearAlgebra.Vector<float32>
type Ball =
{R : float32;
X : DataStreamItem}
and I expect all instances of type DataStreamItem to have D dimensions.
My question is in the interests of algorithm development and debugging since such shape-mismatche-bugs can be a headache to pin down but should be a non-issue when the algorithm is up-and-running:
Is there a way, in either F# or Haskell, to constrain DataStreamItem and / or Ball to have dimensions of D? Or do I need to resort to pattern matching on every calculation?
If the latter is the case, are there any good, light-weight paradigms to catch such constraint violations as soon as they occur (and that can be removed when performance is critical)?
Edit:
To clarify the sense in which D is constrained:
D is defined such that if you expressed the algorithm of the function main(DataStream) as a computation graph, all of the intermediate calculations would depend on the dimension of D for the execution of main(DataStream). The simplest example I can think of would be a dot-product of M with DataStreamItem: the dimension of DataStream would determine the creation of dimension parameters of M
Another Edit:
A week later, I find the following blog outlining precisely what I was looking for in dependant types in Haskell:
https://blog.jle.im/entry/practical-dependent-types-in-haskell-1.html
And Another:
This reddit contains some discussion on Dependent Types in Haskell and contains a link to the quite interesting dissertation proposal of R. Eisenberg.
Neither Haskell not F# type system is rich enough to (directly) express statements of the sort "N nested instances of a recursive type T, where N is between 2 and 6" or "a string of characters exactly 6 long". Not in those exact terms, at least.
I mean, sure, you can always express such a 6-long string type as type String6 = String6 of char*char*char*char*char*char or some variant of the sort (which technically should be enough for your particular example with vectors, unless you're not telling us the whole example), but you can't say something like type String6 = s:string{s.Length=6} and, more importantly, you can't define functions of the form concat: String<n> -> String<m> -> String<n+m>, where n and m represent string lengths.
But you're not the first person asking this question. This research direction does exist, and is called "dependent types", and I can express the gist of it most generally as "having higher-order, more powerful operations on types" (as opposed to just union and intersection, as we have in ML languages) - notice how in the example above I parametrize the type String with a number, not another type, and then do arithmetic on that number.
The most prominent language prototypes (that I know of) in this direction are Agda, Idris, F*, and Coq (not really the full deal AFAIK). Check them out, but beware: this is kind of the edge of tomorrow, and I wouldn't advise starting a big project based on those languages.
(edit: apparently you can do certain tricks in Haskell to simulate dependent types, but it's not very convenient, and you have to enable UndecidableInstances)
Alternatively, you could go with a weaker solution of doing the checks at runtime. The general gist is: wrap your vector types in a plain wrapper, don't allow direct construction of it, but provide constructor functions instead, and make those constructor functions ensure the desired property (i.e. length). Something like:
type Stream4 = private Stream4 of DataStreamItem
with
static member create (item: DataStreamItem) =
if item.Length = 4 then Some (Stream4 item)
else None
// Alternatively:
if item.Length <> 4 then failwith "Expected a 4-long vector."
item
Here is a fuller explanation of the approach from Scott Wlaschin: constrained strings.
So if I understood correctly, you're actually not doing any type-level arithmetic, you just have a “length tag” that's shared in a chain of function calls.
This has long been possible to do in Haskell; one way that I consider quite elegant is to annotate your arrays with a standard fixed-length type of the desired length:
newtype FixVect v s = FixVect { getFixVect :: VU.Vector s }
To ensure the correct length, you only provide (polymorphic) smart constructors that construct from the fixed-length type – perfectly safe, though the actual dimension number is nowhere mentioned!
class VectorSpace v => FiniteDimensional v where
asFixVect :: v -> FixVect v (Scalar v)
instance FiniteDimensional Float where
asFixVect s = FixVect $ VU.singleton s
instance (FiniteDimensional a, FiniteDimensional b, Scalar a ~ Scalar b) => FiniteDimensional (a,b) where
asFixVect (a,b) = case (asFixVect a, asFixVect b) of
(FixVect av, FixVect bv) -> FixVect $ av<>bv
This construction from unboxed tuples is really inefficient, however this doesn't mean you can write efficient programs with this paradigm – if the dimension always stays constant, you only need to wrap and unwrap the once and can do all the critical operations through safe yet runtime-unchecked zips, folds and LA combinations.
Regardless, this approach isn't really widely used. Perhaps the single constant dimension is in fact too limiting for most relevant operations, and if you need to unwrap to tuples often it's way too inefficient. Another approach that is taking off these days is to actually tag the vectors with type-level numbers. Such numbers have become available in a usable form with the introduction of data kinds in GHC-7.4. Up until now, they're still rather unwieldy and not fit for proper arithmetic, but the upcoming 8.0 will greatly improve many aspects of this dependently-typed programming in Haskell.
A library that offers efficient length-indexed arrays is linear.

'Missing'(?) feature in programming languages? [duplicate]

This question already has answers here:
Is there a language with constrainable types?
(8 answers)
Closed 7 years ago.
Let's take Haskell as an example, as it gets the closest to what I'm about to describe of the languages I know.
A type, Int for example, can be viewed as the set of all possible values (of that type).
Why is it that we only get to work with very specific sets?
Int, Double, etc... and not with all their subsets in the type system.
I would love a language where we can define arbitrary types like Int greater than 5. Are there examples of such languages? If not, why not?
You are looking for Dependent types. Idris, Agda and Coq are well known in this category.
You can actually mostly define that in Haskell because it's basically an Int plus some semantics. For example, you have to decide what you're going to do with subtractions that go beneath the threshold, like what (-) 6 7 gives. (A common convention with Peano arithmetic is to give 0 -- so in this case it would return 6, the least value of the system.) You also need to choose whether you're going to error on a fromInteger 3 or else whether you're going to store, say, newtype IntGT5 = IntGT5 (Maybe Int) instead of newtype IntGT5 = IntGT5 Int. You have to write all of the typeclass instances yourself, and once you've done that you'll have an appropriate type.
If you've got an abiding interest in this problem, two things to pay attention to are liquid types and subtyping. Let me tell you a little about the latter.
Alan Kay invented OOP to be something different than what it is (he wanted every program to be written as a network of communicating computers), but what it turned out to be in the modern world is basically a way to do complex subtyping. "Duck typing", for example, is about creating an "intersection type" of a bunch of really general types (like `things with a "waddle" method, things with a "quack" method) which other types are subtypes of. So subtyping is very naturally OOP.
The thesis I've linked you to points out another interesting thing about subtyping: you can use subtypes to erase the distinction between type and value. Of course, making types into values does not need subtyping; it is e.g. already the case in the Python language, where you can call type(x) to get an object which represents the type of the object. But the interesting thing is that as subtypes go, you can just define a type 3 :: Int (3 of kind Int) which is the type of all Ints which are equal to 3. It is in essence a unit/void type which is a subtype of a bigger class. By blurring the distinction between 3 of kind Int and 3 of type Int you get every value being a type as well. You could then do something JSON-like with this approach, where {x: Int, y: 3 :: Int} is a type of objects containing two properties x and y where x is any Int and y is the integer 3 :: Int.
The Ada language supports ranged numeric types. I can't really comment intelligently on this language, but I've been led to understand by folks who know it that the range checks are enforced by runtime checks inserted by the compiler.
Relational database theory also has concepts that tie to this. The set of values that an attribute can take is its domain, which is a function of its data type and the constraints on it. Actual RDBMSs often do not implement these concepts in their full generality, though.
Dependent types are a much more general system that can encode this sort of constraint, and many others. My capsule explanation of dependent types is like this: they are a way of designing a programming language that contains proofs as first-class values. A proof is a value that, by virtue its existence and the rules that it obeys, guarantees the truth of a proposition (a.k.a. "sentence," "statement," etc.).
So under dependent types, you can have a type x > 5 whose values are proofs that x is larger than five. If you have an actual value of this type, then it really is true that x is larger than five, and thus the code where this proof is in scope may safely proceed under that assumption. You can package up the number and the proof together using a dependent sum type that we can notate as ∃x : Int. x > 5, whose values are pairs such that:
The first element is an integer x
The second element is a proof of x > 5

Handling incremental Data Modeling Changes in Functional Programming

Most of the problems I have to solve in my job as a developer have to do with data modeling.
For example in a OOP Web Application world I often have to change the data properties that are in a object to meet new requirements.
If I'm lucky I don't even need to programmatically add new "behavior" code (functions,methods). Instead I can declarative add validation and even UI options by annotating the property (Java).
In Functional Programming it seems that adding new data properties requires lots of code changes because of pattern matching and data constructors (Haskell, ML).
How do I minimize this problem?
This seems to be a recognized problem as Xavier Leroy states nicely on page 24 of "Objects and Classes vs. Modules"
- To summarize for those that don't have a PostScript viewer it basically says FP languages are better than OOP languages for adding new behavior over data objects but OOP languages are better for adding new data objects/properties.
Are there any design pattern used in FP languages to help mitigate this problem?
I have read Phillip Wadler's recommendation of using Monads to help this modularity problem but I'm not sure I understand how?
As Darius Bacon noted, this is essentially the expression problem, a long-standing issue with no universally accepted solution. The lack of a best-of-both-worlds approach doesn't stop us from sometimes wanting to go one way or the other, though. Now, you asked for a "design pattern for functional languages", so let's take a shot at it. The example that follows is written in Haskell, but isn't necessarily idiomatic for Haskell (or any other language).
First, a quick review of the "expression problem". Consider the following algebraic data type:
data Expr a = Lit a | Sum (Expr a) (Expr a)
exprEval (Lit x) = x
exprEval (Sum x y) = exprEval x + exprEval y
exprShow (Lit x) = show x
exprShow (Sum x y) = unwords ["(", exprShow x, " + ", exprShow y, ")"]
This represents simple mathematical expressions, containing only literal values and addition. With the functions we have here, we can take an expression and evaluate it, or show it as a String. Now, say we want to add a new function--say, map a function over all the literal values:
exprMap f (Lit x) = Lit (f x)
exprMap f (Sum x y) = Sum (exprMap f x) (exprMap f y)
Easy! We can keep writing functions all day without breaking a sweat! Algebraic data types are awesome!
In fact, they're so awesome, we want to make our expression type more, errh, expressive. Let's extend it to support multiplication, we'll just... uhh... oh dear, that's going to be awkward, isn't it? We have to modify every function we just wrote. Despair!
In fact, maybe extending the expressions themselves is more interesting than adding functions that use them. So, let's say we're willing to make the trade-off in the other direction. How might we do that?
Well, no sense doing things halfway. Let's up-end everything and invert the whole program. What does that mean? Well, this is functional programming, and what's more functional than higher-order functions? What we'll do is replace the data type representing expression values with one representing actions on the expression. Instead of choosing a constructor we'll need a record of all possible actions, something like this:
data Actions a = Actions {
actEval :: a,
actMap :: (a -> a) -> Actions a }
So how do we create an expression without a data type? Well, our functions are data now, so I guess our data needs to be functions. We'll make "constructors" using regular functions, returning a record of actions:
mkLit x = Actions x (\f -> mkLit (f x))
mkSum x y = Actions
(actEval x + actEval y)
(\f -> mkSum (actMap x f) (actMap y f))
Can we add multiplication more easily now? Sure can!
mkProd x y = Actions
(actEval x * actEval y)
(\f -> mkProd (actMap x f) (actMap y f))
Oh, but wait--we forgot to add an actShow action earlier, let's add that in, we'll just... errh, well.
At any rate, what does it look like to use the two different styles?
expr1plus1 = Sum (Lit 1) (Lit 1)
action1plus1 = mkSum (mkLit 1) (mkLit 1)
action1times1 = mkProd (mkLit 1) (mkLit 1)
Pretty much the same, when you're not extending them.
As an interesting side note, consider that in the "actions" style, the actual values in the expression are completely hidden--the actEval field only promises to give us something of the correct type, how it provides it is its own business. Thanks to lazy evaluation, the contents of the field may even be an elaborate computation, performed only on demand. An Actions a value is completely opaque to external inspection, presenting only the defined actions to the outside world.
This programming style--replacing simple data with a bundle of "actions" while hiding the actual implementation details in a black box, using constructor-like functions to build new bits of data, being able to interchange very different "values" with the same set of "actions", and so on--is interesting. There's probably a name for it, but I can't quite seem to recall...
I've heard this complaint more than a few times, and it always confuses me. The questioner wrote:
In Functional Programming it seems
that adding new data properties
requires lots of code changes because
of pattern matching and data
constructors (Haskell, ML).
But this is by and large a feature, and not a bug! When you change the possibilities in a variant, for example, the code that accesses that variant via pattern-matching is forced to consider the fact that new possibilities have arisen. This is useful, because indeed you do need to consider whether that code needs to change to react to the semantic changes in the types it manipulates.
I would argue with the claim that "lots of code changes" are required. With well-written code, the type system usually does an impressively good job of bringing to the fore the code that needs to be considered, and not much more.
Perhaps the problem here is that it's hard to answer the question without a more concrete example. Consider providing a piece of code in Haskell or ML that you're not sure how to evolve cleanly. I imagine you'll get more precise and useful answers that way.
This tradeoff is known in the programming-language-theory literature as the expression problem:
The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts).
Solutions have been put forward, but I haven't studied them. (Much discussion at Lambda The Ultimate.)
In Haskell at least I would make an abstract data type. That is create a type that doesn't export constructors. Users of the type loose the ability to pattern match on the type and you have to provide functions for working with the type. In return you get a type that is easier to modify without changing code written by users of the type.
If the new data imply no new behaviour, as in an application where we are asked to add a "birthdate" field to a "person" resource and then all we have to do is to add it to a list of fields that are part of the person resource, then it's easy to solve in both the functional and OOP world. Just don't treat the "birthdate" as part of your code; it's just part of your data.
Let me explain: if the birthdate is something that implies a different application behaviour, e.g. that we do something differently if the person is underage, then in OOP we would add a birthdate field to the person class, and in FP we would add similarly a birthdate field to a person data structure.
If there is no behaviour attached to "birthdate" then there should be no field named "birthdate" in the code. A data structure such as a dictionary (a map) would hold the various fields. Adding a new one would require no program changes, no matter if you it's OOP or FP. Validations would be added similarly, by attaching a validation regexp or using a similar validation little language to express in data what the validation behaviour should be.

What uses have you found for higher-rank types in Haskell? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
Higher rank types look like great fun. From the Haskell wikibook comes this example:
foo :: (forall a. a -> a) -> (Char,Bool)
foo f = (f 'c', f True)
Now we can evaluate foo id without the compiler exploding. This example is quickly followed in the book by the real-world example I have seen in a few other places: the ST monad and runST. That's pretty cool.
But I have yet to come across a situation where I solve a problem by writing my own function with an argument of higher-rank. Have you? What examples do you have of rank-2 or rank-n polymorphism in the wild?
Weirich and Washburnn's "Boxes go Bananas"! (paper, slides)
Here is a very crude and probably slightly inaccurate explanation of what it's all about: given an inductive type, BGB lets you represent the space of functions from that type which are "positive" -- they never case-discriminate on their arguments. At most they include their arguments as part of other values (usually of the same type).
Weirich+Washburn use this to get a probably-adequate HOAS representation of the lambda calculus in -XRankNTypes Haskell (has anybody proven it adequate yet?).
I use it here (warning: messy code) to turn a
(forall g . GArrow g => g () x -> g () y)
into a
(forall g . GArrow g => g x y)
This works because the rank-2 polymorphic type can't "inspect" the structure of its argument -- all it can do is "paste" that argument into bigger structures. Some trickery lets me figure out where the pasting happens, and then I thread the pasting point(s) (if any) back out to the input of the GArrow.
You can't do this with the Control.Arrow class, because the whole Haskell function space "leaks" into it via arr.
Take a look at functions like withRepoLock in the Darcs source.
Darcs has support for multiple repository formats, and that support is expressed via a typeclass. So you can write code that is generic over repository formats. When actually reading an on-disk repository you want to dispatch to that code through some common code that figures out what format the repository is in and chooses the right typeclass instantiation.
It may be that you have encountered problems where higher ranked types would be useful, but failed to realise it. For instance in the Darcs example they could easily have implemented it without higher ranked types. Instead there would have been preconditions on some functions that the caller would have to make sure they comply with, such as choosing the right instantiation of a function for the repository format.
The advantage of the higher ranked type is that it transfers responsibility for getting this right from the programmer to the compiler. With the conventional approach, if a Darcs developer made a mistake with the repository type the result would either be a run-time error or else corrupted data. With the higher ranked type the developer gets a type error at compile time.
A relatively-simple example of higher-ranked types in action can be found in Luke Palmer's IO-free splittable supply - he shows how
to make the use of simple unique-value supplies easier:
runSupply :: (forall a. Eq a => Supply a -> b) -> b

Resources