Closed sametmax closed 8 years ago
Hey @sametmax,
I thought about what you're trying to achieve and I think it misses the point of Something and Nothing adding another value (or_else) they have to carry with them.
I thought maybe you can use a descriptor. Define an object that has a descriptor that uses maybe and encapsulates the default value.
Here's an example. Say you're reading videos info from YouTube:
youtube_video_json_string = """
{"snippet": {"playlistId": "UU6lVC6W94JnpNrBKWEeQfyQ", "thumbnails": {"default": {"url": "https://i.ytimg.com/vi/ujY6nwmpevo/default.jpg", "width": 120, "height": 90}, "high": {"url": "https://i.ytimg.com/vi/ujY6nwmpevo/hqdefault.jpg", "width": 480, "height": 360}, "medium": {"url": "https://i.ytimg.com/vi/ujY6nwmpevo/mqdefault.jpg", "width": 320, "height": 180}, "maxres": {"url": "https://i.ytimg.com/vi/ujY6nwmpevo/maxresdefault.jpg", "width": 1280, "height": 720}, "standard": {"url": "https://i.ytimg.com/vi/ujY6nwmpevo/sddefault.jpg", "width": 640, "height": 480}}, "title": "Stocks App Take I", "resourceId": {"kind": "youtube#video", "videoId": "ujY6nwmpevo"}, "channelId": "UC6lVC6W94JnpNrBKWEeQfyQ", "publishedAt": "2014-10-25T14:01:06.000Z", "channelTitle": "Eran Kampf", "position": 0, "description": "Early take of Stocks"}, "kind": "youtube#playlistItem", "etag": "iDqJ1j7zKs4x3o3ZsFlBOwgWAHU/46J7teBS1LTh87m1e4lwFDavz4E", "id": "UUwuIl3s_oR92MeZOEXU9uq7fuRwnm09L5"}
"""
You'll probably want to encapsulate that response to an object:
class YoutubeResponse(object):
def __init__(self, json_string):
self._json = json.loads(json_string)
id = MaybeDescriptor('_json', 'id')
title = MaybeDescriptor('_json', 'snippet', 'title')
description = MaybeDescriptor('_json', 'snippet', 'description', default='no description...')
This way you pass around nice objects instead of dicts, and you encapsulate the _orelse information you wanted.
The code for MaybeDescriptor (which I'm considering adding to pymaybe):
class MaybeDescriptor(object):
def __init__(self, *path, default=None):
self.path = path
self.default_value = default
def __get__(self, instance, owner):
maybe_value = maybe(instance)
for p in self.path:
maybe_value = getattr(maybe_value, p) or maybe_value[p]
return maybe_value.or_else(self.default_value)
What do you think about this solution?
EDIT: Having to put 'json' as the first argument of each Descriptor is annoying. I'm thinking of setting a convention that its always called root and Descriptor will always use __root as first argument
class MaybeObject():
def __init__(self, data):
self.__data
class MaybeDescriptor(object):
def __init__(self, *path, default=None):
self.path = path
self.default_value = default
def __get__(self, instance, owner):
maybe_value = maybe(instance)
maybe_value = getattr(maybe_value, '__data')
for p in self.path:
maybe_value = getattr(maybe_value, p) or maybe_value[p]
return maybe_value.or_else(self.default_value)
class YoutubeResponse(object):
id = MaybeDescriptor('id')
title = MaybeDescriptor('snippet', 'title')
description = MaybeDescriptor('snippet', 'description', default='no description...')
Then again maybe you're just better off doing initialisation of values explicitly in init and not lazily via descriptor:
class YoutubeResponse(object):
def __init__(self, data):
self.id = maybe(data)['id'].get()
self.title = maybe(data)['snippet']['title'].get()
self.description = maybe(data)['snippet']['description'].or_else('no description...')
That would require to craft every object in a way that it accommodate the use of maybe (hence tying them to it) VS just using maybe when some more general part of my program use the object.
Storing another value to be carried on down the call stack with the Something\Nothing objects is kinda messy and defeats the purpose of single responsibility of Something and Nothing. They should represent just that a value or a non-value... and not carry around an extra value.
The best way to implement this functionality is to have another object that wraps this functionality. An Optional (or maybe a Promise is a better name?) class that wraps a maybe() instance and a default. Something like
class Promise(object):
def __init__(self, value, default=None):
self.__value = maybe(value) # just in case, to make sure value is Something\Nothing
self.__default = default
def get(self):
return self.__value.or_else(self.__default)
That makes sense.
For some of my work, I'm considering exposing some objects as Maybe objects instead of just dict like objects. In some specific cases I'll need to provide and or_else() value to the user so he or she doesn't have to set it him or herself.
We could imagine something like:
This way I can even abstract this part of the process, but still allow a user to override it if needed.
What do you think ?