Haskell has a resticted syntax to define type families:
(1) type family Length (xs :: [*]) where
(2) Length '[] = 0
(3) Length (x ': xs) = 1 + Length xs
On the lines (2) and (3) on the left side of the equal sign (=) we only have simple pattern matching.
On the right side of the equal sign we have just type level function application and as a syntactic sugar
there are type operators ((+) in line (3)).
There are no guards, no case expressions, no if-then-else syntax, no let and where's
and there is no partial function application.
This is not a problem, as the missing case expression can be replaced by a specialized type level function,
that pattern matches on the different cases, the missing if-then-else syntax can be replaced by the If
function of the Data.Type.Bool
package.
Looking at some examples we see, that pattern matching syntax on the type level has at least one
additional feature, not available in normal Haskell value level functions:
(1) type family Contains (lst :: [a]) (elem :: a) where
(2) Contains (x ': xs) (x) = 'True
(3) Contains '[] (x) = 'False
(4) Contains (x ': xs) (y) = Contains xs (y)
In line (2) we use twice the variable x. Line (2) evaluates to 'True, if the head of the list of the first parameter
is equal as the second parameter.
If we do the same thing on a value level function, GHC answers with a Conflicting definitions for 'x' error.
In value level functions we must add an Eq a => context to compile the function.
Type level pattern matching seems to work similar as unification back in the old times of Prolog.
I unsuccessfully googled for some documentation about the syntax of type level functions.
Why does GHC not require something like an a ~ b type equality constraint in the definition of the Contains type family?
Is type equality always available?
Has the type family syntax other additional features, that are unavailable on the value level?
Where is this documented?
Haskell's type-level language is a purely first-order language, in which "application" is just another constructor, rather than a thing which computes. There are binding constructs, like forall, but the notion of equality for type-level stuff is fundamentally mere alpha-equivalence: structural up to renaming of bound variables. Indeed, the whole of our constructor-class machinery, monads, etc relies on being able to take an application m v apart unambiguously.
Type-level functions don't really live in the type-level language as first-class citizens: only their full applications do. We end up with an equational (for the ~ notion of equality) theory of type-level expressions in which constraints are expressed and solved, but the underlying notion of value that these expressions denote is always first-order, and thus always equippable with equality.
Hence it always makes sense to interpret repeated pattern variables by a structural equality test, which is exactly how pattern matching was designed in its original 1969 incarnation, as an extension to another language rooted in a fundamentally first-order notion of value, LISP.
Related
So I've been reading about coinduction a bit lately, and now I'm wondering: are Haskell lists inductive or coinductive? I've also heard that Haskell doesn't distinguish the two, but if so, how do they do so formally?
Lists are defined inductively, data [a] = [] | a : [a], yet can be used coinductively, ones = a:ones. We can create infinite lists. Yet, we can create finite lists. So which are they?
Related is in Idris, where the type List a is strictly an inductive type, and is thus only finite lists. It's defined akin to how it is in Haskell. However, Stream a is a coinductive type, modeling an infinite list. It's defined as (or rather, the definition is equivalent to) codata Stream a = a :: (Stream a). It's impossible to create an infinite List or a finite Stream. However, when I write the definition
codata HList : Type -> Type where
Nil : HList a
Cons : a -> HList a -> HList a
I get the behavior that I expect from Haskell lists, namely that I can make both finite and infinite structures.
So let me boil them down to a few core questions:
Does Haskell not distinguish between inductive and coinductive types? If so, what's the formalization for that? If not, then which is [a]?
Is HList coinductive? If so, how can a coinductive type contain finite values?
What about if we defined data HList' a = L (List a) | R (Stream a)? What would that be considered and/or would it be useful over just HList?
Due to laziness, Haskell types are both inductive and coinductive, or, there is no formal distinguishment between data and codata. All recursive types can contain an infinite nesting of constructors. In languages such as Idris, Coq, Agda, etc. a definition like ones = 1 : ones is rejected by the termination checker. Laziness means that ones can be evaluated in one step to 1 : ones, whereas the other languages evaluate to normal form only, and ones does not have a normal form.
'Coinductive' does not mean 'necessarily infinite', it means 'defined by how it is deconstructed', wheras inductive means 'defined by how it is constructed'. I think this is an excellent explanation of the subtle difference. Surely you would agree that the type
codata A : Type where MkA : A
cannot be infinite.
This is an interesting one - as opposed to HList, which you can never 'know' if it is finite or infinite (specifically, you can discover in finite time if a list is finite, but you can't compute that it is infinite), HList' gives you a simple way to decide in constant time if your list is finite or infinite.
In a total language like Coq or Agda, inductive types are those whose values can be torn down in finite time. Inductive functions must terminate. Coinductive types, on the other hand, are those whose values can be built up in finite time. Coinductive functions must be productive.
Systems that are intended to be useful as proof assistants (like Coq and Agda) must be total, because non-termination causes a system to be logically inconsistent. But requiring all functions to be total and inductive makes it impossible to work with infinite structures, thus, coinduction was invented.
So the purpose of inductive and coinductive types is to reject possibly non-terminating programs. Here's an example in Agda of a function which is rejected because of the productivity condition. (The function you pass to filter could reject every element, so you could be waiting forever for the next element of the resulting stream.)
filter : {A : Set} -> (A -> Bool) -> Stream A -> Stream A
filter f xs with f (head xs)
... | true = head xs :: filter f (tail xs)
... | false = filter f (tail xs) -- unguarded recursion
Now, Haskell has no notion of inductive or coinductive types. The question "Is this type inductive or coinductive?" is not a meaningful one. How does Haskell get away without making the distinction? Well, Haskell was never intended to be consistent as a logic in the first place. It's a partial language, which means that you're allowed to write non-terminating and non-productive functions - there's no termination checker and no productivity checker. One can debate the wisdom of this design decision but it certainly renders induction and coinduction redundant.
Instead, Haskell programmers are used to reasoning informally about a program's termination/productivity. Laziness lets us work with infinite data structures, but we don't get any help from the machine to ensure that our functions are total.
To interpret type-level recursion one needs to find a "fixed point" for the
CPO-valued list functor
F X = (1 + A_bot * X)_bot
If we reason inductively, we want the fixed point to be "least". If coinductively, "greatest".
Technically, this is done by working in the embedding-projection subcategory of CPO_bot, taking e.g. for the "least" the colimit of the diagram of embeddings
0_bot |-> F 0_bot |-> F (F 0_bot) |-> ...
generalizing the Kleene's fixed point theorem. For the "greatest" we would take the limit of the diagram of the projections
0_bot <-| F 0_bot <-| F (F 0_bot) <-| ...
It however turns out that the "least" is isomorphic to the "greatest", for any F. This is the "bilimit" theorem (see e.g. Abramsky's "Domain Theory" survey paper).
Perhaps surprisingly, it turns out that the inductive or coinductive flavor comes from the liftings applied by F instead of the least/greatest fixed points. For instance, if x is the smashed product and # is the smashed sum,
F X = 1_bot # (A_bot x X)
would have as a bilimit the set of finite lists (up to iso).
[I hope I got the liftings right -- these are tricky ;-) ]
Can you write polymorphic list functions such as cons, nil and reverse on the STLC?
To elaborate the question, in order to write cons, in Haskell, you need polymorphic types:
cons : ∀ a . a -> List a -> List a
There is no such thing as ∀ on the STLC, so I'd think it is obvious that polymorphic list functions are impossible, i.e., you need to write a new definition of cons for each specialized list type. But what if you leave the type annotation out, and instead have this:
cons = ...
a = cons 1 []
b = cons "" []
The inferencer can identify that cons : Int -> List Int -> List Int on the first case, and cons : String -> List String -> List String on the second case - no forall needed. Under this point of view, looks like polymorphic list functions aren't a problem on the STLC - you just can't annotate their types.
No, it is not possible to write polymorphic functions on STLC. In the definition of STLC there's no universally quantified types. In order to represent polymorphic lists you need some language with type abstraction, like System F.
In your question you mixed (please correct me, if I understand it wrong) the STLC with an inference algorithm for it. If you want polymorphic types, you need a language with type abstraction and application (in order to have type instantiation).
Type inference algorithms (like the ones used by Haskell or ML) consider a restrict version of System F, that is usually called Mini (or Core) ML. Such restrictions are motivated by the undecidability of type inference and checking for System F (details here). More details about this can be found on:
Chapter 23 of Types and programming languages
A simple applicative language: Mini-ML. This article presents the language Mini-ML: its syntax, semantics, type system and type-inference algorithm. It's old, but very readable.
Practical type inference for arbitrary rank types presents a more expressive fragment of System F and discuss its implementation in Haskell.
Why do we need Control.Lens.Reified? Is there some reason I can't place a Lens directly into a container? What does reify mean anyway?
We need reified lenses because Haskell's type system is predicative. I don't know the technical details of exactly what that means, but it prohibits types like
[Lens s t a b]
For some purposes, it's acceptable to use
Functor f => [(a -> f b) -> s -> f t]
instead, but when you reach into that, you don't get a Lens; you get a LensLike specialized to some functor or another. The ReifiedBlah newtypes let you hang on to the full polymorphism.
Operationally, [ReifiedLens s t a b] is a list of functions each of which takes a Functor f dictionary, while forall f . Functor f => [LensLike f s t a b] is a function that takes a Functor f dictionary and returns a list.
As for what "reify" means, well, the dictionary will say something, and that seems to translate into a rather stunning variety of specific meanings in Haskell. So no comment on that.
The problem is that, in Haskell, type abstraction and application are completely implicit; the compiler is supposed to insert them where needed. Various attempts at designing 'impredicative' extensions, where the compiler would make clever guesses where to put them, have failed; so the safest thing ends up being relying on the Haskell 98 rules:
Type abstractions occur only at the top level of a function definition.
Type applications occur immediately whenever a variable with a polymorphic type is used in an expression.
So if I define a simple lens:[1]
lensHead f [] = pure []
lensHead f (x:xn) = (:xn) <$> f x
and use it in an expression:
[lensHead]
lensHead gets automatically applied to some set of type parameters; at which point it's no longer a lens, because it's not polymorphic in the functor anymore. The take-away is: an expression always has some monomorphic type; so it's not a lens. (You'll note that the lens functions take arguments of type Getter and Setter, which are monomorphic types, for similar reasons to this. But a [Getter s a] isn't a list of lenses, because they've been specialized to only getters.)
What does reify mean? The dictionary definition is 'make real'. 'Reifying' is used in philosophy to refer to the act of regarding or treating something as real (rather than ideal or abstract). In programming, it tends to refer to taking something that normally can't be treated as a data structure and representing it as one. For example, in really old Lisps, there didn't use to be first-class functions; instead, you had to use S-Expressions to pass 'functions' around, and eval them when you needed to call the function. The S-Expressions represented the functions in a way you could manipulate in the program, which is referred to as reification.
In Haskell, we don't typically need such elaborate reification strategies as Lisp S-Expressions, partly because the language is designed to avoid needing them; but since
newtype ReifiedLens s t a b = ReifiedLens (Lens s t a b)
has the same effect of taking a polymorphic value and turning it into a true first-class value, it's referred to as reification.
Why does this work, if expressions always have monomorphic types? Well, because the Rank2Types extension adds a third rule:
Type abstractions occur at the top-level of the arguments to certain functions, with so-called rank 2 types.
ReifiedLens is such a rank-2 function; so when you say
ReifiedLens l
you get a type lambda around the argument to ReifiedLens, and then l is applied immediately to the the lambda-bound type argument. So l is effectively just eta-expanded. (Compilers are free to eta-reduce this and just use l directly).
Then, when you say
f (ReifiedLens l) = ...
on the right-hand side, l is a variable with polymorphic type, so every use of l is immediately implicitly assigned to whatever type arguments are needed for the expression to type-check. So everything works the way you expect.
The other way to think about is that, if you say
newtype ReifiedLens s t a b = ReifiedLens { unReify :: Lens s t a b }
the two functions ReifiedLens and unReify act like explicit type abstraction and application operators; this allows the compiler to identify where you want the abstractions and applications to take place well enough that the issues with impredicative type systems don't come up.
[1] In lens terminology, this is apparently called something other than a 'lens'; my entire knowledge of lenses comes from SPJ's presentation on them so I have no way to verify that. The point remains, since the polymorphism is still necessary to make it work as both a getter and a setter.
What is the precise definition of the term "type expression" as used in Thompson's "Haskell - the craft of functional programming" book ? It seems that this term is not defined in the book.
It is for example not listed in the index. Also, I tried to search for a clear explicit definition in the book but found nothing.
Related question: is a type variable a type expression?
Let's consider for example the following type (expression?):
a->b
Now, if I replace a with c, then will c->b be an instance of a->b ?
The terminology "type expression" is used for example on page 314 (third edition of the book):
As sets of types, we look for the intersection of the sets given by
(a, [Char] ) and (Int, [b] ) . How can we work out a description of
this intersection? Before we do this, we revise and introduce some
terminology.
Recall that an instance of a type is given by replacing
a type variable or variables by type expressions. A type expression
is a common instance of two type expressions if it is an instance of
each expression.
The most general common instance of two expressions
is a common instance mgci with the property that every other common
instance is an instance of mgci.
Now we can describe the
intersection of the sets given by two type expressions. It is called
the unification of the two, which is the most general common instance
of the two type expressions.
If you want to think of types as "things", then one can talk about the words we write to describe them, words like Int and Cont (r -> a) (Maybe q) as a language to describe those things. A similar distinction lies between the number 3 and the Haskell code fragment 3.
In the same way that 3 is a (value) expression denoting the number 3, Integer is a type expression denoting the type of integers.
A list in Haskell might look like this:
data List a = Nil | Cons a (List a)
A type theoretic interpretation is:
λα.μβ.1+αβ
which encodes the list type as the fixed point of a functor. In Haskell this could be represented:
data Fix f = In (f (Fix f))
data ListF a b = Nil | Cons a b
type List a = Fix (ListF a)
I'm curious about the scope of the earlier μ binder. Can a name bound in an outer scope remain available in an inner scope? Is, say, the following a valid expression:
μγ.1+(μβ.1+γβ)γ
...perhaps it's the same as:
μβ.μγ.1+(1+γβ)γ
...but then how would things change when the name is reused:
μβ.μγ.1+(μβ.1+γβ)γ
Are the above all regular types?
Scoping of μ works no different from other binders, so yes, all your examples are valid. They are also regular, because they do not even contain a λ. (*)
As for equality, that depends on what sort of μ-types you have. There are basically two different notions:
equi-recursive: in that case, the typing rules assume an equivalence
μα.T = T[μα.T / α]
i.e., a recursive type is considered equal to its one-level 'unrolling', where the μ is removed and the μ-bound variable is replaced by the type itself (and because this rule can be applied repeatedly, one can unroll arbitrary many times).
iso-recursive: here, no such equivalence exists. Instead, a μ-type is a separate form of type with its own expression forms to introduce and eliminate it -- they are usually called roll and unroll (or fold and unfold), and are typed as follows:
roll : T[μα.T / α] → μα.T
unroll : μα.T → T[μα.T / α]
These must be applied explicitly on the term level to mirror the equation above (once for each level of unrolling).
Functional languages like ML or Haskell usually use the latter for their interpretation of datatypes. However, the roll/unroll is built into the use of the data constructors. So each constructor is an injection into an iso-recursive type composed with an injection into a sum type (and inversely when matched).
Your examples are all different under the iso-recursive interpretation. The first and the third are the same under an equi-recursive interpretation, because the outer μ just disappears when you apply the above equivalence.
(*) Edit: An irregular recursive type is one whose infinite expansion does not correspond to a regular tree (or equivalently, can not be represented by a finite cyclic graph). Such a case can only be expressed with recursive type constructors, i.e., a λ that occurs under a μ. For example, μα.λβ.1+α(β×β) -- corresponding to the recursive equation t(β) = 1+t(β×β) -- would be irregular, because the recursive type constructor α is recursively applied to a type "larger" than its argument, and hence every application is a recursive type that "grows" indefinitely (and consequently, you cannot draw it as a graph).
It's worth noting, however, that in most type theories with μ, its bound variable is restricted to ground kind, so cannot express irregular types at all. In particular, a theory with unrestricted equi-recursive type constructors would have non-terminating type normalisation, so type equivalence (and thus type checking) would be undecidable. For iso-recursive types you'd need higher-order roll/unroll, which is possible, but I'm not aware of much literature investigating it.
Your μ type expressions are valid. I believe your types are regular as well since you only use recursion, sum, and products.
The type
T1 = μγ.1+(μβ.1+γβ)γ
does not look equal to
T2 = μβ.μγ.1+(1+γβ)γ
since inr (inr (inl *, inr (inl *)), inl *) has the second type but not the first.
The last type
T3 = μβ.μγ.1+(μβ.1+γβ)γ
is equal to (α-converting the first β)
μ_.μγ.1+(μβ.1+γβ)γ
which is, unfolding the top-level μ,
μγ.1+(μβ.1+γβ)γ
which is T1.
Basically, the scope of μ-bound variables follows the same rules of λ-bound variables. That is, the value of each occurrence of a variable β is provided by the closest μβ on top of it.