From HaskellWiki
Revision as of 16:58, 25 May 2009 by Josef (talk | contribs) (Some comments from Josef Svenningsson)

Jump to: navigation, search

Fun with Type Functions

Oleg Kiselyov, Ken Shan, and Simon Peyton Jones have a draft paper

which will appear in the proceedings of Tony Hoare's 75th birthday celebration.

Abstract. Tony Hoare has always been a leader in writing down and proving properties of programs. To prove properties of programs automatically, the most widely used technology today is by far the ubiquitous type checker. Alas, static type systems inevitably exclude some good programs and allow some bad ones. This dilemma motivates us to describe some fun we've been having with Haskell, by making the type system more expressive without losing the benefits of automatic proof and compact expression.

Haskell's type system extends Hindley-Milner with two distinctive features: polymorphism over type constructors and overloading using type classes. These features have been integral to Haskell since its beginning, and they are widely used and appreciated. More recently, Haskell has been enriched with type families, or associated types, which allows functions on types to be expressed as straightforwardly as functions on values. This facility makes it easier for programmers to effectively extend the compiler by writing functional programs that execute during type-checking.

This paper gives a programmer's tour of type families as they are supported in GHC today.

This Wiki page is a discussion page for the paper. If you are kind enough to read this paper, please help us by jotting down any thoughts it triggers off. Things to think about:

  • What is unclear?
  • What is omitted that you'd like to see?
  • Do you have any cool examples that are of a somewhat different character than the ones we describe? (If so, do explain the example on this page!)

Deadline is sometime in June 2009.

You can identify your entries by preceding them with four tildes. Doing so adds your name, and the date. Thus:

Simonpj 08:42, 19 April 2007 (UTC) Note from Simon

If you say who you are in this way, we'll be able to acknowledge your help in a revised version of the paper.

Add comments here (newest at the top):

Byorgey 20:07, 19 May 2009 (UTC)

  • p. 32:
    • "powerful, but the" -> "powerful, but they"
  • p. 33:
    • "commonly mentioned on Haskell mailing lists pitfall of type functions" -> "one pitfall of type functions commonly mentioned..."
    • "the compiler should chose" -> "should the compiler choose"

BerniePope 06:18, Fri 15 May 2009 (UTC) Typo in Appendix B.

"GHC should noat" -> "GHC should not"

tanimoto 02:17, 15 May 2009 (UTC) Typo in references

In reference [32], page 30, Oleg's last name is misspelled as "Kiselov".

Ryani 23:01, 14 May 2009 (UTC) Fun paper! Comments:

I was writing a generic finite map a while ago and determined that the generic memoized trie was better in almost all cases; it was simpler semantically and didn't have a significant performance difference. Then you have "type Map k v = Table k (Maybe v)". Is it worth calling out this special case in its own section?

Also, in respose to ChrisKuklewicz, I think the type for "cons" is correct, but perhaps one instance should be given as an example.

Dave Menendez 16:52, 14 May 2009 (UTC) On page 11, you refer to a "specialised instance for Table Int that uses some custom (but infinite!) tree representation for Int." Was this meant to be Integer? Surely any tree representation for Int would be large but finite.

Peter Verswyvelen and I have been working on some type family fun to give us generalised partial application (even to the point of being able to cope with giving arguments, but not a function). I don't know if it really makes any interesting point that you didn't already in the paper, but it's certainly fun...

{-# LANGUAGE TypeFamilies, EmptyDataDecls, TypeOperators, FlexibleInstances, FlexibleContexts #-}

module Burn2 where

newtype V a = V a -- A value
data    B a = B   -- A value we don't want to provide yet

-- Type level homogenous lists (well just tuples in a list-like syntax really)
data Nil a = Nil
data a :& b = !a :& !b

infixr 5 :& 

class Apply funargs where
  type Result funargs :: *
  apply :: funargs -> Result funargs

instance (Apply (V b :& rest), a ~ c) => Apply (V (a->b) :& V c :& rest) where
  type Result  (V (a->b) :& V c :& rest) = Result (V b :& rest)
  apply (V f :& V a :& rest) = apply $ V (f a) :& rest

instance (Apply (V b :& rest), a ~ c) => Apply (B (a->b) :& V c :& rest) where
  type Result (B (a->b) :& V c :& rest) = (a->b) -> Result (V b :& rest)
  apply (B :& V a :& rest) = \f -> apply $ V (f a) :& rest

instance (Apply (V b :& rest), a ~ c) => Apply (V (a->b) :& B c :& rest) where
  type Result  (V (a->b) :& B c :& rest) = a -> Result (V b :& rest)
  apply (V f :& B :& rest) = \a -> apply $ V (f a) :& rest

instance (Apply (V b :& rest), a ~ c) => Apply (B (a->b) :& B c :& rest) where
  type Result (B (a->b) :& B c :& rest) = (a->b) -> a -> Result (V b :& rest)
  apply (B :& B :& rest) = \f a -> apply $ V (f a) :& rest

instance Apply (V a :& Nil b) where
  type Result  (V a :& Nil b) = a
  apply (V a :& Nil) = a

instance Apply (B a :& Nil b) where
  type Result  (B a :& Nil b) = B a
  apply (B :& Nil) = B

v1 = apply (V 1 :& Nil)
f1 = apply (B :& Nil)
v2 = apply (V negate :& V 1 :& Nil)
f3 = apply (V negate :& B :& Nil)
v3 = apply (V f3 :& V 1 :& Nil)

Beelsebob 13:04, 14 May 2009 (UTC)

End of section 2.2, I think "cons :: a -> [b] -> [ResTy a b]" should be "cons :: a -> [b] -> ResTy a b"

ChrisKuklewicz 15:28, 14 May 2009 (UTC)

End of page 19 with footnote 9. I could not simply copy and paste the URL because of a stray space after the '-' in arguments.lhs

ChrisKuklewicz 16:08, 14 May 2009 (UTC)

Typo "Mounier" --> "Monnier"

Tom Schrijvers 11:11, 15 May 2009 (UTC)

Contrary to what the introductions say, polymorphism over type constructors was not part of Haskell from the beginning. They were only introduced after Mark Jones showed how to do it in Gofer.

Augustss 14:30, 15 May 2009 (UTC)

Why do you say "Obviously, we want to declare algebraic data kinds, ..."? What's obvious about that? Many of us think that's the wrong way, and you should instead provide a way to lift ordinary data type to the kind level.

Augustss 15:36, 15 May 2009 (UTC)

I was really fascinated by section 5.2 where you track state in the types using a parameterized monad. However, the example code you use is rather underwhelming since it can be implemented by much simpler means. If there's only a fixed number of locks then they can be tracked using a fixed tuple and the whole type functions business is a bit superfluous (albeit still nice). To really reap the benefits of the machinery you set up you ought to have a function for creating new locks dynamically. Here's an example of how it can be done:

type family Length p
type instance Length Nil = Zero
type instance Length (Cons l p) = Succ (Length p)

type family NewLock p
type instance NewLock Nil = Cons Unlocked Nil
type instance NewLock (Cons l p) = Cons l (NewLock p)

newLock :: Nat (Length p) =>
           LockM p (NewLock p) (Lock (Length p))
newLock = LockM (return mkLock)

In order for this to work nicely we need a new run function as well. I've used an ordinary type class to check that all locks are unlocked, since this really is a predicate on the state and not a function.

class AllUnlocked a

instance AllUnlocked Nil
instance AllUnlocked p => AllUnlocked (Cons Unlocked p)

runNew :: AllUnlocked locks => LockM Nil locks a -> IO a
runNew = unLockM

I think the section becomes more convincing if you add dynamic creation of locks. Not to mention the increased sex appeal :-)

Josef 16:58, 25 May 2009 (UTC)

It would be very nice if you published a bundle or repository somewhere containing the Haskell source from the paper. At the very least it would be nice with a comment on which version of GHC one should have and what flags are required. The reason I'm asking is that I had a bit of trouble with this. For instance, just saying {-# LANGUAGE TypeFamilies #-} didn't enable type equality constraints in the parser. I had to add ScopedTypeVariables which felt rather arbitrary.

Josef 16:58, 25 May 2009 (UTC)