On 30/10/2022 14:01, Julieta Shem wrote: > I wrote the classes > > class Empty: > ... > class Pair: > ... > > (*) How to build a stack? > > These Lisp-like sequences are clearly a stack.
That is a very important observation. A Pair IS-A Stack(sort of). If you had a stack you could create a Pair from it certainly. > So far so good, but when it comes to building a better user interface > for it I have no idea how to do it. I think one of the purposes of OOP > is to organize code hierarchically so that we can reuse what we wrote. One of the purposes of classes certainly. I'm not so sure it's a purpose of OOP. They are not the same thing. A class is a programming construct OOP is a programming style. Classes facilitate OOP but can be used outside of OOP too. > So I tried to make a Stack by inheriting Pair. But you said above that a Pair was a Stack. Inheritance implies an IS-A relationship, so Stack inheriting Pair would mean that a Stack was a Pair. That is not really true. A Stack could use a Pair (or many of them) but it is not a Pair. Trying to use inheritance inappropriately is one of the biggest (and commonest) mistakes in OOP. It invariably leads to complications. If in doubt use delegation instead. > class Stack(Pair): > pass > >>>> Stack(1, Empty()) > Stack(1, Empty()) > > Then I wrote pop and push. > >>>> Stack(1, Empty()).pop() > 1 > >>>> Stack(1, Empty()).push(2) > Stack(2, Stack(1, Empty())) > > So far so good. Now let me show you what I can't do. > > (*) The difficulty of encapsulating a union > > The Lisp-like sequences we're building here are union-like data > structures. A /sequence/ is either Empty() or Pair(..., /sequence/). I > have not found a way to represent this either-or datastructure with a > class. For example, there is no way right now to build an empty Stack > by invoking the Stack constructor. > >>>> Stack() > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > TypeError: Pair.__init__() missing 2 required positional arguments: 'first' > and 'rest' > The usual Python approach to such things is to put some default values(often None but could be an Empty instance in your case) in the init() method parameter list. Then test if the inputs are None and if so take the appropriate action. > class Pair: > def __init__(self, first=Empty(), rest=Empty()): Like this. > if not isinstance(rest, Pair) and not isinstance(rest, Empty): > raise ValueError("rest must be Empty or Pair") > self.first = first > self.rest = rest > def fromIterable(it): > if len(it) == 0: > return Empty() > else: > return Pair(it[0], Pair.fromIterable(it[1:])) > def __str__(self): > return "{}({!r}, {})".format(self.__class__.__name__, self.first, > str(self.rest)) > def __repr__(self): > return str(self) > def __len__(self): > return 1 + self.rest.__len__() > > class Empty: > def __len__(self): > return 0 > def __str__(self): > return "Empty()" > def __repr__(self): > return self.__str__() > def __new__(clss): > if not hasattr(clss, "saved"): > clss.saved = super().__new__(clss) > return clss.saved > > class Stack(Pair): > def pop(self): > return self.first > def push(self, x): > return Stack(x, self) -- Alan G Author of the Learn to Program web site http://www.alan-g.me.uk/ http://www.amazon.com/author/alan_gauld Follow my photo-blog on Flickr at: http://www.flickr.com/photos/alangauldphotos -- https://mail.python.org/mailman/listinfo/python-list