I've been working on a project in python, and I needed to represent a certain numeric type. I need to represent the concept of some or all. Then I need to be able to add these amounts.
Coming to this juncture I found myself really wishing I was writing in Haskell. In Haskell I could build myself a nice little functor like so:
data Amount = All | Some Integer deriving (Eq, Show)
instance Num Amount where
All + _ = All
_ + All = All
(Some a) + (Some b) = Some (a + b)
(This gives a warning because Amount is not a complete instance of Num however the point here is just a minimal demonstration)
I could even use Maybes instead of creating my own datatype:
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
import Control.Applicative (liftA2)
type Amount = Maybe Integer
instance Num Amount where
(+) = liftA2 (+)
This second solution isn't as clean, it requires some language extensions and uses the word Nothing instead of All which could be confusing, but it is still an OK solution in my book.
This is a very easy task in Haskell with solutions I consider elegant and clear. However in Python there are no clean solutions that I can come up with. Every solution leaves me with a bad taste in my mouth. Here are the 4 solutions I've come up with:
1
class Amount(object):
def __init__(self,repr):
self.repr = repr
def __add__(self,other):
if self.repr == "all" or other.repr == "all":
return Amount("all")
else:
return Amount(self.repr + other.repr)
1.1
class Amount(object):
def __init__(self,repr):
if repr == "all" or type(repr) is int:
self.repr = repr
else:
raise Exception('Amount must be either an int or "all".')
def __add__(self,other):
if self.repr == "all" or other.repr == "all":
return Amount("all")
elif type(self.repr) is type(other.repr) is int:
return Amount(self.repr + other.repr)
else:
raise Exception('Attempted to add malformed Amounts.')
2
class Amount(object):
def __init__(self,isAll,value):
self.isAll = isAll
self.value = value
def __add__(self,other):
return Amount(self.isAll or other.isAll, self.value + other.value)
2.1
class Amount(object):
def __init__(self,isAll,value):
self.isAll = isAll
if self.isAll:
self.value = 0
else:
self.value = value
def __add__(self,other):
if self.isAll or other.isAll:
return Amount(True, 0)
else:
return Amount(False, self.value + other.value)
3
class All(object):
def __init__(self):pass
def __add__(self, other):
return All()
class Some(object):
def __init__(self, quantity):
self.quantity = quantity
def __add__(self, other):
if type(other) is Some:
return Some(self.quantity + other.quantity)
elif type(other) is All:
return All()
else:
return Some(other + self.quantity)
4
class All(object):
def __init__(self):pass
def __add__(self, other):
return All()
Each solution has its own problems. The first solutions 1 and 1.1 are basically stringly typed solutions our class is a container which contains either a string or a integer. Solution 1.1 adds some safety by checking things but overall I find this solution very hacky.
The second solution is pretty much a union type with two parameters. It is much less hackish than the first, and is actually a solution I have seen other people use to similar problems in the past, but I still feel that it is a pretty bad fit. My big problem is that there is always extra data hanging around. Even though it isn't very much data and doesn't really cause any bloat, this makes me uncomfortable that a malformed function or method might try accidentally use the value property of an All, leading issues that may be very difficult to debug. 2.1 attempts to fix this by constantly setting the value to 0 when isAll is on, but I still feel am not very satisfied. Overall I feel that this solution is also pretty hackish.
The third solution implements two different types, one for All and one for Some. This is probably my favorite solution since it kind of approximates my Haskell solution but it still has issues. The big issue is that Some and All are not the same type, they are different. I'm not a big fan of mixing types and any time I perform a type check I'm going to have the issue that these things that are meant to go together have different types.
The final solution is similar to the last except it gets rid of the Some type. This is nice in that it behaves much in the way the third solution does but is much slimmer. That being said it alleviates none of the existing issues with the last solution
Which of these solutions is best and why?
Noneis a good candidate for yourAll(akin toMaybesolution). That said, I am afraid that the question is off-topic here. \$\endgroup\$