Showing posts with label architecture. Show all posts
Showing posts with label architecture. Show all posts

Monday, September 01, 2025

Typed duck typing in Python with Gemini

(picture By Klem - This vector image was created with Inkscape by Klem, and then manually edited by Mnmazur, Public Domain, https://commons.wikimedia.org/w/index.php?curid=3213322 )

Dynamic typing of duck typing


Two months ago I drafted a Python tracer library with Gemini, then I rewrote it from scratch (here). It is however "clunky". Yet I do not worry, as Python is a computer scientist dream language, and in Python one can quickly implement something meta and probably slow, but then use a cache or two to make it fast enough. That is what I did last week: I sat in front of my screen, stared at it for 30m thinking through my options, and then asked Gemini to draft the concept. And while the result is missing some logic, it is however doing what I want: it dynamically wraps "types" around duck-typed operation. I could try to explain what it does... but I do not need to. Instead, I share what I told Gemini. The key desires are the following:

  • let's make a cached wrapper factory, that when given an object, first is looks in the cache, and returns the instantiated wrapper with the object if found, otherwise it uses the above greedy cover, where given a list of mixin classes, it dynamically creates a "best covering wrapper" that is remembered in the cache, and used to wrap the object.
  • Ok, that works. Now we want to extend the concept to include some __init__ specify logic. First we need to make sure the __init__ is special and not part of the covering logic. Then we will assume that all mixins will get the target_obj as the first positional argument, and other mixin arguments will come through kwargs arguments.
  • Nice. Now a last little bit: A list of additional "context specific" mixins can be given to create_wrapper, these will be will be added as parents in addition to the coverage mixins. Now the caching needs to be extended, as a same target object might be wrapped with different additional context mixins.

          (the full set of prompts is given below)

The idea is the following: Python naturally favors a duck-typing style of programming, yet duck typing is hard to scale without boilerplate code. Therefore one approach is to "find" what types support the duck typing. The types (classes in Python), can then be used to scale the program's design. In effect, we are dynamically typing the duck typing. Caching is then used to ensure performance.

Open vs closed sets in software design

Traditional designs ask the developer to choose a closed set of types, and then enables an open set of data to be created. Trace oriented designs work the other way around: we design a closed set of data traces, from these we derive an open set of types. This is the first level of Yin and Yang of software design. Something you might when you want to optimize high-performance code, such as an array heavy ML system. This technique is explained from a slightly different angle here.

 The Gemini prompt strategy

I did really think about the design for a half an hour before starting on the prompt session below. As mentioned before (the-art-of-prompt and master-class-techniques-for-llm), you really cannot just randomly chat with an LLM to get world class output, you need to think through your strategy, and the key part is that you are really trying to achieve a "one shot" chat with no corrections needed. The reality is that the billions of facts known by the LLM will only collapse into to the desired solutions if you pull it carefully, almost like a glass blower will shape molten class. This comparison is especially true: you only have a limited number prompts before the quality of the LLM fails you! 
The chat start with pedantic info... this is important! You really must ensure that the base concepts are well aligned with end goal of the task!
Note my very chatty style: don't forget: the LLM is trained on human text, you want to resonate with that humanity to pull out quality output.

The prompts (producing this code)

  1. What are the rules of double inherentence in python? supposing I have A and B both deriving from X, and now I create Z deriving from A and B, is is a special case for Python, or does it just take it as a normal class hierarchy and simply takes Z of A when there is an ambiguity (supposing that A comes before B in the parent list).
  2. Ok, clear. What about the __init__, and multiple inherentence, is it correct that I need to explicity call the parent __init__ , What about X, who calls it?
  3. Ok, but then what arugments? Am I pretty much foced to use kvargs so that the parents can pick and choose their arguments?
  4. Finally, how do I inspect an object to gets its class hierarchy?
  5. Great. What would be the name of programming style where I rely heavily on the MRO? In effect I take all the MRO, and build a dynamic logic that depends on their interdependent structures?
  6. so maybe what I want to do is "dynamic mixin" as their will be only dynamic assemblies of interdependent classes.
  7. Right, and to be double shure, I can provide any base class list I want to that type constructor, python will work out the MRO that ensures the proper class parent hierarchy order, right?
  8. Does itertool have a "generic" tree iterator? I want to get all classes of a module, ideally I want the recursive call to have already been written, and I feed it functions that depend on inspect. Can I do that?
  9. And how about: given an object, and a list of classes, use a greedy search to find the subset of classes that "covers" the methods of the given object. The greedy search always tries to first cover with classes with a larger number of methods. Note however that it is not allow to cover using a class with a method that is not present in the given object.
  10. Ok, so we follow up on the wrapper: let's make a cached wrapper factory, that when given an object, first is looks in the cache, and returns the instantiated wrapper with the object if found, otherwise it uses the above greedy cover, where given a list of mixin classes, it dynamically creates a "best covering wrapper" that is remembered in the cache, and used to wrap the object.
  11. Ok, that works. Now we want to extend the concept to include some __init__ specify logic. First we need to make sure the __init__ is special and not part of the covering logic. Then we will assume that all mixins will get the target_obj as the first positional argument, and other mixin arguments will come through kwargs arguments.
  12. Nice. Now a last little bit: A list of additional "context specific" mixins can be given to create_wrapper, these will be will be added as parents in addition to the coverage mixins. Now the caching needs to be extended, as a same target object might be wrapped with different additional context mixins.
  13. Right, last steps is to remove the underscore filter, as the mixins are specifically designed for dunder logic.
  14. Right, but LengthMixin does not need to test for hasattr __len__ as it will only be brought in dynamically.
  15. Et voila. If you remember, we wrote a first wrapper version about two month ago. The issue with that first approach without this dynamic mixin logic is tha the wrapper logic becomes clunky with special cases. This double mixin approach allows the wrapper logic to be added very generically, although at the cost of a more complex and slower execution.
  16. Let's add an MIT license with my name James Litsios and year 2025
  17. You can add a comment under the license where you share the credit of the work!

What next?

I will rewrite my tracing library for the fourth time using the above!

All original content copyright James Litsios, 2025.

Wednesday, July 30, 2025

Design versus Architecture in software development

First try (from Gemini 2.5 Flash)

Software architecture defines the system's fundamental structure, outlining its major components, their high-level relationships, and the overarching principles and quality attributes (like scalability or security) that guide its construction. It's the "what" and "why" of the system's foundational choices, which are costly to change.

In contrast, software design focuses on the detailed internal workings of individual components and modules, specifying their logic, data structures, and algorithms to implement the architectural vision effectively. It's the "how" of putting those foundational pieces together, generally with a lower cost of modification.

Second try (with my input)

Software architecture defines the system's fundamental structure, outlining its major components, their high-level relationships, and the overarching principles and quality attributes (like scalability or security) that guide its construction. It's the "what" and "why" of the system's foundational choices, which are costly to change and, by their nature, represent the unique, large-scale blueprint of a specific system, and are not directly composable with other architectures.

In contrast, software design focuses on the detailed internal workings of individual components and modules, specifying their logic, data structures, and algorithms to implement the architectural vision effectively. It's the "how" of putting those foundational pieces together, with decisions often leading to reusable and composable solutions that can be combined to build larger features or even integrated into different systems.

Why the difference (Gemini):

My answer was a reflection of common knowledge, while your insight stemmed from highly specialized and deep expertise in higher-order functional programming.

Some remarks:

  • Architecture is often brought in from external sources, less often developed.
  • Architecture is an expensive risk: a bad architecture can doom a project, a poor architecture can explode your costs.
  • A team that does not master their architecture and designs is a big risk, and not all teams have the skills needed for chosen architectures and designs.
  • Design fits in an agile process. Architecture is often hard to fit in an agile process.

All original content copyright James Litsios, 2025.

Saturday, March 12, 2022

Thinking about parallel and distributed computation

A classical way to look at parallel / distributed systems is in terms of:

  1. Work-Efficiency
  2. Parallelism
  3. Locality 
This is one of those inconsistent triads (or trilemma), where only two among three properties can be chosen. For example, a system can have both work-efficiency and parallelism, or parallelism and locality but cannot be fully work-efficient, parallel and local.

This is not the only inconsistent triads to think about when designing scalable systems.
Here are more:
  1. Velocity
  2. Resources
  3. Work
  1. Momentum
  2. Quality
  3. Traffic
And of course the CAP theorem's:
  1. Consistency
  2. Availability
  3. Partition tolerance
And the important:
  1. Fast
  2. Good
  3. Cheap
All of these are ultimately important, yet some of these are more practically useful. Especially when you realise that you can weaken the inconsistent of these triads by introducing additional complementary properties. 

To illustrate this with examples:
  • Two people must work together vs an engineer and a sales person must work together
  • Two time constraints must be considered vs a measurement must last one microsecond and be done in the next two weeks.
The idea here is that by clarifying dependencies and scales we are 'loosening' our constraints. The second example above is about distinguishing what is short versus long (in duration), and also that a measurement is typically done before things are said to done. Therefore if we partition what is of 'short duration' vs what is of 'long duration', or what is 'done before' and what is 'done after', we soften the inconsistent of the triads above. For example, we could have:
  • Work-Efficiency of all durations
  • Parallelism of long durations
  • Locality of short durations
As a result, we 'never have all three' for short durations, or for long durations.

In their most primitive form, these are binary properties. For example, things can be:
  1. Small
  2. Large
  1. Slow
  2. Fast
  1. Short
  2. Long
  1. Light
  2. Heavy
  1. Precise
  2. Approximate
  1. Predictable
  2. Unpredictable
  1. Efficient
  2. Inefficient
  1. Up-stream
  2. Down-stream
  1. Present
  2. Future
And so on...

These properties make sense when they are properties that build the inconsistent triad. Using arbitrary properties would not work. For example, tall vs short does not help us. And to note the triads above are different. For example, efficiency is not part of the CAP theorem, but does effect the other triads (and this depends also on how efficiency is measured).

Finally, all of these inconsistent triads are interdependent. You want to understand how. 
For example, my previous post was in fact based on two tightly depend triads, which I illustrate as follows with their 'limits' within a distributed system view:


The cost of a cloud architecture is strongly tied how these complementary inconsistent triads are approached.

All original content copyright James Litsios, 2022.

Wednesday, September 25, 2019

Agile process vs Data Management

Modern architecture, for example for large cloud systems, have much in common with agile processes. You may know from past posts that I love agile processes. One reason is that agile processes have mathematical roots. The other is that when you know how to yield them, they deliver. I could talk for two hours on the subject! Here, for your comfort, the focus is just on the basic tie between an agile process and the architecture of modern data management.
Here is the video (9m):


Pictures from the video

Hereunder, the main pictures of the video, in case you want to refer to them in your comments:

1) Concrete vs Abstract (horizontally) and Static vs Dynamic (vertically):

(Note how I say concrete, but write "contract" in the video. That is a mistake, and what happens when you invent smart contracts and focus much on them!).

2) Agile process "functional schema" and data management "functional schema" side by side:


3) Waterfall process (red):

4) Agile iterative process (blue):

5) Traditional database architecture (red):

6) Modern data management architecture (blue):


Yes, in case you are wondering: my left hand is holding my iphone as I record. 
As always, comments are welcome, especially from old friends!

Friday, June 20, 2014

Scalable Designs for Multicore


I put together this presentation on how to design software that scales on multicores.
It uses examples in Java, yet the basic learning is same in all languages. (In fact, I learned it in C++).
(Original video on YouTube is: https://www.youtube.com/watch?v=op2Fh-iiKoA ).

Here's the summaries:

Basic multicore scalability (in Java)

  • Parallelize with threads and thread pools
  • Use enough threads (but not too many)
  • Thread pools assure execution on multiple cores
  • Keep same objects on same cores (as much as possible)
  • Avoid having multiple tasks working on the same data
  • Avoid locks (ex. attribute synchronized)
  • Avoid cache line contention

Patterns to manage lock conflicts

  1. Structured parallelism
  2. Non-blocking data structures
  3. Immutable or purely functional data structures

Patterns to manage core affinity

  1. Tasks last as long as program
  2. Tasks last as long as service they implement
  3. Tasks are dynamically extended

All original content copyright James Litsios, 2014.

Thursday, February 17, 2011

Types versus architecture

There are three invariants in a software system:
  • Code
  • Types
  • The architecture
One old rule is: more types, less code. This is because the less "sophisticated" type provides less "support" to the code, and therefore you need more code to keep things in control. Another way to put this is that the invariants provided by the use of more types allows the code to be more simple.
For example, long ago I experienced this type of code simplification with the use of the record/struct moving to C and Pascal from early basic and FORTRAN (and assembly). And again with classes and objects moving to C++. And again with variant types and later monads with functional programming.

Another rule is: better architecture, less code. I have repeated this mantra for many years and yet thinking about it, I am not even very sure I have a good definition for architecture. I use to say: 'architecture is what does not change in the system'. Now maybe I should say: 'it's what does not change and what is not a type'. This then begs the question: 'If I can capture more of my system in its types, does that make the architecture more simple'?

I am starting to get the feeling that architecture is a little bit like dynamic typing: you use it when you do not know better. I am tempted to try to introduce a new rule: better typing, less architecture". I will see if I can make that rule stick!