Skip to content

Instantly share code, notes, and snippets.

@toolittlecakes
Last active April 17, 2024 03:44
Show Gist options
  • Save toolittlecakes/d538dfedb2169f9e3ccea6bb83b11f5a to your computer and use it in GitHub Desktop.
Save toolittlecakes/d538dfedb2169f9e3ccea6bb83b11f5a to your computer and use it in GitHub Desktop.
Inspired by this article about unobvious usage of polymorphism in order to embrace functional approach to create alternative solution
import syslog
from dataclasses import dataclass
from typing import Callable, Generic, Literal, Protocol, Type, TypeVar, TypeVarTuple
# Initial solution with classes
#
# class Filter(Protocol):
# def filter(self, message: str) -> bool: ...
# class TextFilter(Filter):
# def __init__(self, pattern: str):
# self.pattern = pattern
# def filter(self, message: str):
# return self.pattern in message
# class Handler(Protocol):
# def emit(self, message: str): ...
# class FileHandler(Handler):
# def __init__(self, file):
# self.file = file
# def emit(self, message):
# self.file.write(message + "\n")
# self.file.flush()
# class SocketHandler(Handler):
# def __init__(self, sock):
# self.sock = sock
# def emit(self, message):
# self.sock.sendall((message + "\n").encode("ascii"))
# class SyslogHandler(Handler):
# def __init__(self, priority):
# self.priority = priority
# def emit(self, message):
# syslog.syslog(self.priority, message)
# class Logger:
# def __init__(self, filters, handlers):
# self.filters = filters
# self.handlers = handlers
# def log(self, message):
# if not all(filter(message) for filter in self.filters):
# return
# for handler in self.handlers:
# handler(message)
# ----------------------------
# Simple Version
# Filter = Callable[[str], bool]
# Emitter = Callable[[str], None]
# ----------------------------
# Custom Version (functions only)
# def function_protocol[F: Callable](_: F) -> type[F]: ...
# def implement_protocol[P](_: Type[P]) -> Callable[[P], P]:
# def decorator[T](func: T) -> T:
# return func
# return decorator
# @function_protocol
# def Filter(message: str) -> bool: ...
# @function_protocol
# def Emitter(message: str) -> None: ...
# ----------------------------
# More static checks (analog of Protocol for classes (implicit)):
class Filter(Protocol):
def __call__(self, message: str) -> bool: ...
class Emitter(Protocol):
def __call__(self, message: str) -> None: ...
def build_text_filter(pattern: str) -> Filter:
def filter_text(message: str):
return pattern in message
return filter_text
def build_file_emmiter(file) -> Emitter:
def emit(message: str):
file.write(message + "\n")
file.flush()
return emit
def build_socket_emmiter(sock) -> Emitter:
def emit(message: str):
sock.sendall((message + "\n").encode("ascii"))
return emit
def build_syslog_emmiter(priority):
def emit(message: str):
syslog.syslog(priority, message)
return emit
# ----------------------------
# Even more static checks (with explicit Named Functors)
# Analog of classes Interface (explicit)
# class TypeWrapper[F: Callable]:
# __call__: F
# class CallWrapper[F: Callable]:
# def __init__(self, func: F):
# self.func = func
# def __call__(self, *args, **kwargs):
# return self.func(*args, **kwargs)
# class Functor[F: Callable](TypeWrapper[F], CallWrapper[F]): ...
# def adapter[F: Callable](_: F) -> type[Functor[F]]:
# return Functor[F]
# def _(message: str) -> bool: ...
# class Filter(adapter(_)): ...
# def _(message: str) -> None: ...
# class Emitter(adapter(_)): ...
# def build_text_filter(pattern: str) -> Filter:
# def filter_text(message: str):
# return pattern in message
# return Filter(filter_text)
# def build_file_emmiter(file) -> Emitter:
# def emit(message: str):
# file.write(message + "\n")
# file.flush()
# return Emitter(emit)
# def build_socket_emmiter(sock) -> Emitter:
# def emit(message: str):
# sock.sendall((message + "\n").encode("ascii"))
# return Emitter(emit)
# def build_syslog_emmiter(priority):
# def emit(message: str):
# syslog.syslog(priority, message)
# return Emitter(emit)
# ----------------------------
def build_logger(filters: list[Filter], emmiters: list[Emitter]):
def logger(message: str):
if not all(filter(message) for filter in filters):
return
for emit in emmiters:
emit(message)
return logger
log = build_logger(
[build_text_filter("error")],
[
build_file_emmiter(open("error.log", "a")),
build_syslog_emmiter(syslog.LOG_ERR),
],
)
log("error: something went wrong")
log("info: everything is fine")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment