[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: the forward method [dynamic vs. static typing]



Ken Shan writes:
> On 2003-12-11T03:26:54-0500, FranklinChen@cmu.edu wrote:
> > That printing code is not faithful to your intention, which was
> >         data SuperList a = SuperList a (SuperList [a])
> > The code you wrote reflects the intention
> >         data SuperList a = SuperList a (Maybe (SuperList [a]))
> > which is quite different.
> 
> If the printing code I gave is not faithful to my intention, then
> neither is the data structure definition that you gave, even though
> you explicitly asked me whether the definition you gave is faithful to
> my intention.  A C++ pointer can be either null or non-null; non-null
> pointers can be expressed in C++ as references.

True, my compile-time implementation was not faithful to the Haskell,
because I was changing the task and illustrating a tangent point.
 
> > C++'s NULL is a value, not a type, so it is impossible, as you correct
> > indicate, to write a function template using an encoding of Maybe in
> > which NULL is a Nothing and a pointer is a Maybe.
> 
> Are you drawing a distinction between C++ and Haskell here, in that
> C++'s null is a value where as Haskell's Nothing is a type?  But in
> fact, Nothing in Haskell is a value, not a type.

No, I was not drawing such a distinction, since it would be of course
false.  I was speaking loosely with regard to the key point that NULL
in C++ has no type, while Nothing does have a type.  This is actually
a genuine problem that who knows if the C++ committee will eventually
resolve.

Also, given that NULL is not an object, one cannot override methods on
it, to fully simulate sum types in C++.  This is why, in order to
simulate Maybe in C++, you need to define a Nothing class.

Therefore, C++ NULL is pretty evil.

> In any case, I would be happy to switch as you did to the Haskell data
> type definition
> 
>     data SuperList a = SuperList a (Maybe (SuperList [a]))
> 
> However, the two approaches to coding in C++ you presented are not as
> expressive as Haskell type classes.  To be more concrete, let me provide
> not just the above data type definition but also some Haskell code
> manipulating that data type in a way that I (still) don't know how to
> achieve with C++ (templates).

I never claimed C++ was as expressive as Haskell.  We all know it
isn't.

The only reason I brought anything up at all was to address a very
specific point, i.e., the claim that printing could not be made
generic.  I disproved it in two different ways (one of which I
posted), though both could be considered "cheating".

> Here is a complete Haskell program incorporating the data type
> definition above:
> 
> >   module Main where
> 
> >   data SuperList a = SuperList a (Maybe (SuperList [a])) deriving Show
> 
> >   make :: a -> Int -> SuperList a
> >   make a 0 = SuperList a Nothing
> >   make a n = SuperList a (Just (make [a,a] (n - 1)))
> 
> >   main :: IO ()
> >   main = getLine >>= print . make True . read
> 
> The crucial point here is that the "make" function takes an arbitrary
> integer and returns a SuperList of that depth.  The main program ("main"
> above) simply reads an integer (say "n") from standard input and returns
> a SuperList of that depth (namely "make True n").

It would be possible to encode part of the "make" functionality (using
either the static or dynamic method) by using a Factory pattern, but
I'm not going to try, because it would be messy and we already know
that it's not going to be really faithful to the Haskell anyway.

> It does not suffice in C++ to simply define a Show class with a virtual
> member function for printing to an ostream, because that solution does
> not extend to
> 
>   - comparing two SuperLists of possibly different depth for
>     equality (say by overloading operator==);
> 
>   - storing multiple SuperLists of possibly different depth in a single
>     container, say vector.
> 
> Two SuperLists should be compatible for equality comparisons or storage
> in a homogeneous array iff their root type ("a" above) is the same, even
> if they may be of different depth.  Indeed, different branches of the
> same SuperList may be of different depth, so a C++ solution that encodes
> the depth or shape of a SuperList in its type is overly specific.  On
> the other hand, two SuperLists whose root types are different should not
> be eligible for equality comparisons, even if they are both printable to
> ostreams.

The code I posted doesn't do this stuff, of course.  It's the code I
didn't post, but compiles and runs fine, which does the following:
- SuperList<T> for all SuperLists of different depths (and therefore
  storable in a homogeneous container)
- ostream output
- equality comparison

It may be cheating, but the way to do it was to make printing and
equality comparison virtual, and override them appropriately.  It's a
simple-minded encoding of sum types and functions over them by means
of classes with overriding.

A small sample of it:

/*
t = SuperList (1, Just (SuperList ([3, 1, 4, 1, 5, 9], Just (SuperList
([[3, 1, 4, 1, 5, 9], [2, 7, 1, 8, 2, 8]], Nothing)))))
t1 = SuperList (2, Just (SuperList ([3, 1, 4, 1, 5, 9], Just
(SuperList ([[3, 1, 4, 1, 5, 9], [2, 7, 1, 8, 2, 8]], Nothing)))))
u = SuperList (2, Just (SuperList ([3, 1, 4, 1, 5, 9], Nothing)))
(t == t1) = 0
(t == u) = 0
(t1 == u) = 0
(u == u) = 1
*/
  SuperList<int> &t = 
    makeList(one, 
             makeJustList(v, 
                          makeJustLast(lv))); 
 
  SuperList<int> &t1 = 
    makeList(two, 
             makeJustList(v, 
                          makeJustLast(lv))); 
 
  SuperList<int> &u = 
    makeList(two, 
             makeJustLast(v)); 
 
  cout << "t = " << t << endl; 
  cout << "t1 = " << t1 << endl; 
  cout << "u = " << u << endl; 
 
  cout << "(t == t1) = " << (t == t1) << endl; 
  cout << "(t == u) = " << (t == u) << endl; 
  cout << "(t1 == u) = " << (t1 == u) << endl; 
  cout << "(u == u) = " << (u == u) << endl; 

-- 
Franklin