## Monoid to make a configuration dsl¶

If you have read Gabriels presentation, you might think, that I would be trying to create monoid for combining event streams. Unfortunately, I don't think I am hard-core enough to do that in Python.

On the other hand, writing a tiny config library sounds like a bit of harmless fun. So, we will need the mappend function again.

In [1]:
from functools import singledispatch

@singledispatch
def mappend(a, b):
raise Error("Not implemented for" + a)


This lets us define mconcat.

In [2]:
def mconcat(l):
acc = l[0]
for x in l[1:]:
acc = mappend(acc,x)
return acc


For the configuration, we would need:

• lists
• dictionaries
• functions to process things
In [3]:
@mappend.register(list)
def _(a,b):
return a + b

@mappend.register(dict)
def _(a,b):
return {**a, **b}

@mappend.register(mconcat.__class__)
def _(a,b):
def result(*x):
a_r= a(*x)
b_r=b(*x)
return mappend(a_r,b_r)
return result


Now we could create a generic function, i.e. askFor:

In [4]:
def askFor(name):

In [5]:
askAll = mconcat([
])

In [6]:
askAll()

namea
agea
emaila

Out[6]:
{'age': 'a', 'email': 'a', 'name': 'a'}

What we could do now, instead of asking for input manually, we could pass in a config string and parse it. I will first create the config string, with a simple structure "key:value" on each line.

In [7]:
config = """
name:eve
age:16
dance:swing
"""


Now I can create a simple parsing function, where I can input the key, and it will return the value. Actually, it will return a parser that takes the string produces a dictionary with single KV pair, but thats almost the same, just more composable :)

And for now, if it doesn't find the key, it produces empty dictionary.

In [8]:
import re

def parseFor(name):
m = re.search('(?<='+name+').*', config)
if m == None:
return {}
else:
return {name: m.group(0)}

In [9]:
parseAll = mconcat([
parseFor('name'),
parseFor('age'),
parseFor('email')
])

parseAll(config)

Out[9]:
{'age': ':16', 'name': ':eve'}

And because in our parsing functions we get the whole config, we could parse different things as well. For example we could get names of all of the keys.

In [10]:
def getKeys(config):
return {"keys": [x.strip() for x in re.split(":.*\n",config) if x.strip()!= ""]}

In [11]:
parseAll = mconcat([parseFor('name'),getKeys])

parseAll(config)

Out[11]:
{'keys': ['name', 'age', 'dance'], 'name': ':eve'}

### Better config object?¶

Another observation we can make, is that we can create an alternative interpretation of dicitonary mappend, where don't just over-write the latter, but we assume that values of the dictionary are monoids themselves. This means we can mappend them again :-)

In [12]:
@mappend.register(dict)
def _(a,b):
return {**a, **b,**{k:mappend(a[k],b[k]) for k in a if k in b}}



We could then have additional alternative implementation for our function mappend. Our new mappend would return the result of the first function, that doesn't return none.

In [13]:
@mappend.register(mconcat.__class__)
def _(a,b):
def result(*x):
a_r= a(*x)
if a_r!=None:
return a_r;
b_r=b(*x)
return b_r
return result


This way we can flip the building blocks for our little dsl.

In [14]:
def askFor(name):

def parseFor(name):
m = re.search('(?<='+name+').*', config)
if m == None:
return None
return m.group(0)


We could create one more

In [15]:
parseAll = mconcat([
parseFor('name'),
parseFor('age'),
parseFor('email'),
])

parseAll

Out[15]:
{'age': <function __main__._.<locals>.result>,
'email': <function __main__._.<locals>.result>,
'name': <function __main__._.<locals>.result>}

The drawback is, that now we have dictionary of functions, where previously we had just a singe funciton that got us our config. We an still implement that:

In [16]:
def getConfig(parser, config):
return {k:parser[k](config) for k in parser}


Way we implemented this (dictionary keys get mappended and the first return from a funcion wins) we should only be prompted for the email, and rest of this should be parsed from config.

In [18]:
getConfig(parseAll,config)

emaila

Out[18]:
{'age': ':16', 'email': 'a', 'name': ':eve'}
In [19]:
parseAll2 = mconcat([
parseFor('age'),
parseFor('email'),
])

getConfig(parseAll2,config)

emaila

Out[19]:
{'age': ':16', 'email': 'a', 'name': ':eve'}

And now I have a litle dsl where:

• the building blocks are nice, and I can just mconcat them together
• if I want to add new building block, it just needs to

• accept the config-string
• return a KV dict

On the other hand, it has no error handling, it is kind-of inefficient, and in practce would be just a useless toy. But there ae more powerful abstractions that would help us with this. Next time, functors?