Explicit Recursion in Generic Haskell Andres L oh Universiteit - - PowerPoint PPT Presentation

explicit recursion in generic haskell
SMART_READER_LITE
LIVE PREVIEW

Explicit Recursion in Generic Haskell Andres L oh Universiteit - - PowerPoint PPT Presentation

Explicit Recursion in Generic Haskell Andres L oh Universiteit Utrecht andres@cs.uu.nl 26th March 2003 This is joint work with Dave Clarke and Johan Jeuring. Overview What is Generic Haskell? The difference between implicit and


slide-1
SLIDE 1

Explicit Recursion in Generic Haskell

Andres L¨

  • h

Universiteit Utrecht andres@cs.uu.nl 26th March 2003

This is joint work with Dave Clarke and Johan Jeuring.

slide-2
SLIDE 2

Overview

➙ What is Generic Haskell? ➙ The difference between implicit and explicit recursion ➙ How explicit recursion works ➙ Future possibilities

slide-3
SLIDE 3

Generic Haskell

➙ An extension to Haskell ➙ Allows the programmer to define generic functions (and generic datatypes), i.e. functions indexed by a type argument ➙ A generic function can be defined inductively over the structure of datatypes, thus working for all types in a generic way ➙ Generic Haskell is implemented as a preprocessor that translates generic functions into Haskell ➙ Translation proceeds by specialisation ➙ Most of the ideas go back to Ralf Hinze’s several papers about generic programming in Haskell

slide-4
SLIDE 4

Example: Generic equality

equalUnit Unit Unit = True equala + b (Inl a1) (Inl a2) = equala a1 a2 equala + b (Inr b1) (Inr b2) = equalb b1 b2 equala + b = False equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2

➙ The type arguments are specially marked. ➙ Cases are given for a small set of Haskell datatypes:

data Unit = Unit data a + b = Inl a | Inr b data a × b = (a, b)

(Additional constructs deal with constructor names.)

slide-5
SLIDE 5

Generic functions

➙ Generic functions can be specialised to several types. ➙ Special cases for specific datatypes/constructors are possible. ➙ Generic functions can “inherit” cases from other generic functions. ➙ Other examples of generic functions include: mapping,

  • rdering, (de)coding, (un)parsing, generic traversals,
  • perations on type-indexed datatypes.
slide-6
SLIDE 6

Implicit versus explicit recursion

Actually, the equality function used to be written differently in Generic Haskell:

equalUnit Unit Unit = True equal+ eqa eqb (Inl a1) (Inl a2) = eqa a1 a2 equal+ eqa eqb (Inr b1) (Inr b2) = eqb b1 b2 equal+ eqa eqb = False equal× eqa eqb (a1, b1) (a2, b2) = eqa a1 a2 ∧ eqb b1 b2

➙ Less intuitive, but more general. ➙ Type arguments are always simple type constructors. ➙ Kind-indexed type:

equalt :: ∗ :: t → t → Bool equalt :: ∗ → ∗ → ∗ :: ∀a b.(a → a → Bool) → (b → b → Bool) → t a b → t a b → Bool

➙ Type-level application is replaced by value-level application.

equalf a = equalf (equala)

slide-7
SLIDE 7

Key idea

Get the nice syntax of explicit recursion, but keep all advantages of implicit recursion.

slide-8
SLIDE 8

Explicit recursion, implicit dictionaries

equalUnit Unit Unit = True equal+ eqa eqb (Inl a1) (Inl a2) = eqa a1 a2 equal+ eqa eqb (Inr b1) (Inr b2) = eqb b1 b2 equal+ eqa eqb = False equal× eqa eqb (a1, b1) (a2, b2) = eqa a1 a2 ∧ eqb b1 b2

slide-9
SLIDE 9

Explicit recursion, implicit dictionaries

equalUnit Unit Unit = True equal+ equala equalb (Inl a1) (Inl a2) = equala a1 a2 equal+ equala equalb (Inr b1) (Inr b2) = equalb b1 b2 equal+ equala equalb = False equal× equala equalb (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2

We rename the dictionary arguments.

slide-10
SLIDE 10

Explicit recursion, implicit dictionaries

equalUnit Unit Unit = True equala + b equala equalb (Inl a1) (Inl a2) = equala a1 a2 equala + b equala equalb (Inr b1) (Inr b2) = equalb b1 b2 equala + b equala equalb = False equala × b equala equalb (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2

We add variables to the type arguments.

slide-11
SLIDE 11

Explicit recursion, implicit dictionaries

equalUnit Unit Unit = True equala + b . . . (Inl a1) (Inl a2) = equala a1 a2 equala + b . . . (Inr b1) (Inr b2) = equalb b1 b2 equala + b . . . = False equala × b . . . (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2

We forget the dictionary arguments. ➙ Scoped type variables are in red. ➙ Looks like the original definition, but is interpreted in the same way as the current implementation. ➙ Type arguments are type constructors, applied to variables. Always kind ∗.

slide-12
SLIDE 12

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool

slide-13
SLIDE 13

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree :: forbidden!

slide-14
SLIDE 14

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

slide-15
SLIDE 15

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

Let us look at the type for the product case:

equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2

slide-16
SLIDE 16

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

Let us look at the type for the product case:

equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2 equala × b :: ∀a b.a × b → a × b → Bool

slide-17
SLIDE 17

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

Let us look at the type for the product case:

equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2 equala × b :: ∀a b.(equala :: a → a → Bool, equalb :: b → b → Bool) ⇒ a × b → a × b → Bool

slide-18
SLIDE 18

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

Let us look at the type for the product case:

equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2 equala × b :: ∀a b.(equala :: a → a → Bool, equalb :: b → b → Bool) ⇒ a × b → a × b → Bool equalTree a :: ∀a.(equala :: a → a → Bool) ⇒ Tree a → Tree a → Bool

slide-19
SLIDE 19

What about the type?

equalInt :: Int → Int → Bool equal[Char] :: [Char] → [Char] → Bool

For completely specified types of kind ∗, we still have

equalt :: ∗ :: t → t → Bool equalTree a::???

Let us look at the type for the product case:

equala × b (a1, b1) (a2, b2) = equala a1 a2 ∧ equalb b1 b2 equala × b :: ∀a b.(equala :: a → a → Bool, equalb :: b → b → Bool) ⇒ a × b → a × b → Bool equalTree a :: ∀a.(equala :: a → a → Bool) ⇒ Tree a → Tree a → Bool

Dictionary arguments reappear as dependency constraints in the types!

slide-20
SLIDE 20

Satisfying constraints

similar :: Char → Char → Bool similar a b = toLower a ≡ toLower b

slide-21
SLIDE 21

Satisfying constraints

similar :: Char → Char → Bool similar a b = toLower a ≡ toLower b let equala = similar in equalTree a :: Tree Char → Tree Char → Bool

slide-22
SLIDE 22

Satisfying constraints

similar :: Char → Char → Bool similar a b = toLower a ≡ toLower b let equala = similar in equalTree a :: Tree Char → Tree Char → Bool let equala = similar in equalPair a b :: ∀b.(equalb :: b → b → Bool) ⇒ Pair Char b → Pair Char b → Bool

slide-23
SLIDE 23

Satisfying constraints

similar :: Char → Char → Bool similar a b = toLower a ≡ toLower b let equala = similar in equalTree a :: Tree Char → Tree Char → Bool let equala = similar in equalPair a b :: ∀b.(equalb :: b → b → Bool) ⇒ Pair Char b → Pair Char b → Bool let equala = similar in equalPair a a :: Pair Char Char → Pair Char Char → Bool

slide-24
SLIDE 24

Satisfying constraints – continued

let equala = similar in λx → equalTree a Leaf x :: Tree Char → Bool

slide-25
SLIDE 25

Satisfying constraints – continued

let equala = similar in λx → equalTree a Leaf x :: Tree Char → Bool let neqt = not ◦ equalTree a in True :: Bool

slide-26
SLIDE 26

Satisfying constraints – continued

let equala = similar in λx → equalTree a Leaf x :: Tree Char → Bool let neqt = not ◦ equalTree a in True :: Bool let neqt = not ◦ equalTree a in (let equala = True in neqt , let equala = similar in neqt ) :: ∀a.(Tree a → Tree a → Bool, Tree Char → Tree Char → Bool)

slide-27
SLIDE 27

Type signatures

The following type signature is sufficient for generic equality:

equalt :: (generalize a a → (equala :: a → a → Bool) ⇒ a → a → Bool) t

slide-28
SLIDE 28

Type signatures

The following type signature is sufficient for generic equality:

equalt :: (generalize a a → (equala :: a → a → Bool) ⇒ a → a → Bool) t

➙ Basic idea: type on kind ∗ plus all dependencies. ➙ Future work: infer dependency constraints. ➙ More than one dependency?

slide-29
SLIDE 29

Multiple dependencies

importantt :: (generalize a a → (importanta :: a → Bool) ⇒ a → Bool) t

slide-30
SLIDE 30

Multiple dependencies

importantt :: (generalize a a → (importanta :: a → Bool) ⇒ a → Bool) t

  • ptshowt :: (generalize a a →

(optshowa :: a → String, importanta :: a → Bool) ⇒ a → String) t

slide-31
SLIDE 31

Multiple dependencies

importantt :: (generalize a a → (importanta :: a → Bool) ⇒ a → Bool) t

  • ptshowt :: (generalize a a →

(optshowa :: a → String, importanta :: a → Bool) ⇒ a → String) t

  • ptshowa + b (Inl a) =

if importanta a then optshowa a else "..."

slide-32
SLIDE 32

Multiple dependencies

importantt :: (generalize a a → (importanta :: a → Bool) ⇒ a → Bool) t

  • ptshowt :: (generalize a a →

(optshowa :: a → String, importanta :: a → Bool) ⇒ a → String) t

  • ptshowa + b (Inl a) =

if importanta a then optshowa a else "..."

➙ This is very hard to do without explicit recursion. ➙ Generic functions with multiple dependencies occur frequently in in the context of generic traversals or type-indexed datatypes.

slide-33
SLIDE 33

Future possibilities

➙ Do the same transformation on the type level (for type-indexed data types). ➙ Allow complex type patterns. ➙ Allow type patterns of higher kinds. ➙ Higher-order generic functions. ➙ Already mentioned: infer dependency constraints in type signatures of generic functions.

slide-34
SLIDE 34

Conclusions

➙ Explicit recursion is simpler to explain and simpler to use. ➙ Because of the use of dependency constraints, nothing of the generality of the former approach (using implicit recursion) is lost. ➙ Explicit recursion and dependency constraints combine beautifully with other features of Generic Haskell (default cases, generic abstractions). ➙ Many other problems seem to become easier to solve once the type patterns contain arguments. ➙ Generic Haskell is available from

http://www.generic-haskell.org

➙ The type system for explicit recursion is only implemented in a

  • prototype. (Demonstration is possible.)