15

I'm just reading Dependent Types at Work. In the introduction to parametrised types, the author mentions that in this declaration

data List (A : Set) : Set where
  []   : List A
  _::_ : A → List A → List A

the type of List is Set → Set and that A becomes implicit argument to both constructors, ie.

[]   : {A : Set} → List A
_::_ : {A : Set} → A → List A → List A

Well, I tried to rewrite it a bit differently

data List : Set → Set where
  []   : {A : Set} → List A
  _::_ : {A : Set} → A → List A → List A

which sadly doesn't work (I'm trying to learn Agda for two days or so, but from what I gathered it's because the constructors are parametrised over Set₀ and so List A must be in Set₁).

Indeed, the following is accepted

data List : Set₀ → Set₁ where
  []   : {A : Set₀} → List A
  _::_ : {A : Set₀} → A → List A → List A

however, I'm no longer able to use {A : Set} → ... → List (List A) (which is perfectly understandable).

So my question: What is the actual difference between List (A : Set) : Set and List : Set → Set?

Thanks for your time!

Vitus
  • 11,822
  • 7
  • 37
  • 64

1 Answers1

15

I take the liberty to rename the data types. The first, which is indexed on Set will be called ListI, and the second ListP, has Set as a parameter:

data ListI : Set → Set₁ where
  []  : {A : Set} → ListI A
  _∷_ : {A : Set} → A → ListI A → ListI A

data ListP (A : Set) : Set where
  []  : ListP A
  _∷_ : A → ListP A → ListP A

In data types parameters go before the colon, and arguments after the colon are called indicies. The constructors can be used in the same way, you can apply the implicit set:

nilI : {A : Set} → ListI A
nilI {A} = [] {A}

nilP : {A : Set} → ListP A
nilP {A} = [] {A}

There difference comes when pattern matching. For the indexed version we have:

null : {A : Set} → ListI A → Bool
null ([]  {A})     = true
null (_∷_ {A} _ _) = false

This cannot be done for ListP:

-- does not work
null′ : {A : Set} → ListP A → Bool
null′ ([]  {A})     = true
null′ (_∷_ {A} _ _) = false

The error message is

The constructor [] expects 0 arguments, but has been given 1
when checking that the pattern [] {A} has type ListP A

ListP can also be defined with a dummy module, as ListD:

module Dummy (A : Set) where
  data ListD : Set where
    []  : ListD
    _∷_ : A → ListD → ListD

open Dummy public

Perhaps a bit surprising, ListD is equal to ListP. We cannot pattern match on the argument to Dummy:

-- does not work
null″ : {A : Set} → ListD A → Bool
null″ ([]  {A})     = true
null″ (_∷_ {A} _ _) = false

This gives the same error message as for ListP.

ListP is an example of a parameterised data type, which is simpler than ListI, which is an inductive family: it "depends" on the indicies, although in this example in a trivial way.

Parameterised data types are defined on the wiki, and here is a small introduction.

Inductive families are not really defined, but elaborated on in the wiki with the canonical example of something that seems to need inductive families:

data Term (Γ : Ctx) : Type → Set where
  var : Var Γ τ → Term Γ τ
  app : Term Γ (σ → τ) → Term Γ σ → Term Γ τ
  lam : Term (Γ , σ) τ → Term Γ (σ → τ)

Disregarding the Type index, a simplified version of this could not be written with in the Dummy-module way because of lam constructor.

Another good reference is Inductive Families by Peter Dybjer from 1997.

Happy Agda coding!

danr
  • 2,405
  • 23
  • 24
  • Thanks for your answer! There's still one thing I'd like to know (my question might have been a bit ambiguous, I'm afraid): I cannot define `List` as `Set → Set` to prevent inconsistency in the type system, what is the actual mechanism that makes `List (A : Set) : Set` "work"? Because to me (coming from Haskell background), they both seem to be `data List :: * -> * where (...)`, but one works and the other doesn't. Thanks! – Vitus Jan 27 '12 at 16:46
  • 1
    I think you have already stated the reason; to avoid inconsistencies constructors with Set as an argument must necessary belong in Set₁. The parametric data types allows us to write datatypes that with indicies would have to end up one level "higher", and it "works" just because of the well-behaved conditions of parametric datatypes. On the other hand, there is some controversy how "safe" the inductive families are, as the page to the wiki illustrates, and I think the current consensus is to have some small and trusted Agda code that fancy data types can be translated to. – danr Jan 28 '12 at 01:54
  • 1
    Dybjer's distinction between "parameter" and "index" is the thing that you bring out with your `Dummy` module. In his terminology, Term's Gamma argument is most definitely an index. The assertion that "parameters go before the colon, and arguments after the colon are called indices" is misleading (in that it does not characterize the distinction) and arguably false. It is possible (but unwise) to put parameters after the colon. It is possible (and common, and wise) to put indices left of the colon. The key point is that indices may vary node-to-node, but parameters are fixed for a whole value. – pigworker Sep 22 '14 at 16:28