際際滷

際際滷Share a Scribd company logo
Declarative Programming
&
Algebraic Data Types *
Maxim Avanov
maximavanov.com
* from Django's perspective
19thMoscow Django Meetup
Our Goal
1. 束Outsource損 boilerplate (i.e. concentrate on important).
2. Check as much as possible, and as soon as possible.
3. Component coherence.
1. A story of How & What
Concentrate on important
How vs. What
defhandle_article_form(request):
ifrequest.method=='POST':
form=ArticleForm(request.POST)
ifform.is_valid():
save_article(form.cleaned_data)
returnHttpResponseRedirect('/success/')
else:
form=ArticleForm()
returnrender(request,'article_form.html',{'form':form})
How
ifrequest.method=='POST':
form=ArticleForm(request.POST)
ifform.is_valid():
#...
else:
#...
What
#case1
save_article(form.cleaned_data)
returnHttpResponseRedirect('/success/')
#case2
form=ArticleForm()
returnrender(request,'article_form.html',{'form':form})
A few things to worry about
defhandle_article_form(request):
ifrequest.method=='POST':
form=ArticleForm(request.POST)
ifform.is_valid():
save_article(form.cleaned_data)
returnHttpResponseRedirect('/success/')
else:
form=ArticleForm()
returnrender(request,'article_form.html',{'form':form})
束What損 is obscured by 束How損
redundant details
Single Responsibility principle is violated
If we could get rid of How's once and for all,
would we miss them?
Constraint programming
Tribute to Pyramid
fromrhetoricimportview_config,view_defaults
@view_defaults(route_name='articles',renderer='article_form.html')
classArticlesHandler(object):
def__init__(self,request):
self.request=request
@view_config(request_method='GET')
defarticle_form(self):
form=ArticleForm()
return{'form':form}
@view_config(request_method='POST',validate_form=ArticleForm)
defsave_article(self):
save_article(self.request.form.cleaned_data)
returnHttpResponseRedirect('/success/')
@view_config(request_method='POST')
definvalid_article_form(self):
return{'form':self.request.form}
What it actually means
#WehaveanArticlesHandler:ArticlesHandler
#Itrendersatemplate:article_form.html
#Ausershallbeabletoaddnewentries:article_form()
#IfwesubmitvalidArticleForm:save_article()
#IfwesubmitinvalidArticleForm:invalid_article_form()
classArticlesHandler(object):
def__init__(self,request):
self.request=request
defarticle_form(self):
form=ArticleForm()
return{'form':form}
defsave_article(self):
save_article(self.request.form.cleaned_data)
returnHttpResponseRedirect('/success/')
definvalid_article_form(self):
return{'form':self.request.form}
...or in other words
---
view:ArticlesHandler
GET:article_form
POST:
-validate:articles.ArticleForm
view:save_article
-view:invalid_article_form
classArticlesHandler(object):
def__init__(self,request):
self.request=request
defarticle_form(self):
form=ArticleForm()
return{'form':form}
defsave_article(self):
save_article(self.request.form.cleaned_data)
returnHttpResponseRedirect('/success/')
definvalid_article_form(self):
return{'form':self.request.form}
A couple of other examples
Different ways to do the same thing
@view_defaults(route_name='authentication',renderer='auth_form.html')
classAuthenticationHandler(object):
@view_config(request_method='POST',validate_form=EmailAuthForm)
defauth_with_email(self):
#...
@view_config(request_method='POST',validate_form=SMSAuthForm)
defauth_with_sms(self):
#...
@view_config(request_method='POST',validate_form=LoginAuthForm)
defauth_with_login(self):
#...
@view_config(request_method='POST')
defon_invalid_form(self):
#...
API versioning
config.add_route('api.workflows','/api/workflows')
@view_defaults(route_name='api.workflows',api_version='<2.0')
classWorkflowsAPIv1(object):
#...
@view_defaults(route_name='api.workflows',api_version='>=2.0')
classWorkflowsAPIv2(object):
#...
@view_defaults(route_name='api.workflows',renderer='json')
classWorkflowsAPI(object):
@view_config(request_method='POST',api_version='<2.0')
defcreate_new_workflow_v1(self):
#...
@view_config(request_method='POST',api_version='>=2.0')
defcreate_new_workflow_v2(self):
#...
Algebraic Data Types
OCaml ADT example
Watch a 束Caml Trading損 talk by Yaron Minsky at
http://youtu.be/hKcOkWzj0_s?t=31m6s
OCaml ADT example
typeorder={id:int;price:float;size:int;}
typecancel={xid:int;}
typeinstruction=
|Orderoforder
|Cancelofcancel
letfilter_by_oidinstructionsoid=
List.filterinstructions
(funx->matchxwith
|Ordero->o.id=oid
|Cancelc->c.xid=oid)
OCaml ADT example
typeorder={id:int;price:float;size:int;}
typecancel={xid:int;}
typecancel_replace={xr_id:int;new_price:float;new_size:int;}
typeinstruction=
|Orderoforder
|Cancelofcancel
|Cancel_replaceofcancel_replace
letfilter_by_oidinstructionsoid=
List.filterinstructions
(funx->matchxwith
|Ordero->o.id=oid
|Cancelc->c.xid=oid)
WarningP:Thispattern-matchingisnotexhaustive
Hereisanexampleofavaluethatisnotmatched...
2. Trying to reproduce
Check as much as possible, and as soon as possible
Python ADT *
* kind of
Python ADT example
Models (product types)
fromdjango.dbimportmodels
classOrder(models.Model):
tid=models.IntegerField()
price=models.DecimalField(max_digits=16,decimal_places=4)
size=models.IntegerField()
classCancel(models.Model):
xtid=models.IntegerField()
classCancelReplace(models.Model):
xr_tid=models.IntegerField()
new_price=models.DecimalField(max_digits=16,decimal_places=4)
new_size=models.IntegerField()
Python ADT example
Smart Enums (union types)
fromrhetoric.adtimportadt
from.modelsimportOrder,Cancel,CancelReplace
classInstruction(adt):
ORDER=Order
CANCEL=Cancel
CANCEL_REPLACE=CancelReplace
Python ADT example
Cases (match statement)
from.typesimportInstruction
@Instruction.ORDER('filter_by_oid')
deffilter_order_by_oid(order,oid):
returnorder.tid==oid
@Instruction.CANCEL('filter_by_oid')
deffilter_cancel_by_oid(cancel,oid):
returncancel.xtid==oid
@Instruction.CANCEL_REPLACE('filter_by_oid')
deffilter_cancel_replace_by_oid(cr,oid):
returncr.xr_tid==oid
deffilter_by_oid(instructions,oid):
returnlist(filter(
lambdai:Instruction.match(i)['filter_by_oid'](i,oid),
instructions))
Python ADT example
Cases (2)
from.typesimportInstruction
inline_matcher=Instruction.inline_match(
ORDER=(lambdao,oid:o.tid==oid),
CANCEL=(lambdac,oid:c.xtid==oid),
CANCEL_REPLACE=(lambdacr,oid:cr.xr_tid==oid))
deffilter_by_oid_alt(instructions,oid):
returnlist(filter(lambdai:inline_matcher(i)(i,oid),
instructions))
3. Use case: multilingual content
Component coherence
Use case
Define ADT
fromrhetoric.adtimportadt
classLanguage(adt):
ENGLISH='en'
GERMAN='de'
Use case
Register Models
fromdjango.dbimportmodels
from.typesimportLanguage
classIRegionalArticle(models.Model):
classMeta:
abstract=True
title=models.CharField(max_length=140,default='')
text=models.TextField(max_length=65536,default='')
@Language.ENGLISH('db:articles')
classEnglishArticle(IRegionalArticle):
pass
@Language.GERMAN('db:articles')
classGermanArticle(IRegionalArticle):
pass
Use case
Process requests
fromrhetoric.viewimportview_config,view_defaults
from.typesimportLanguage
@view_defaults(route_name='articles.regional.index',renderer='json')
classArticlesHandler(object):
def__init__(self,request,language):
self.request=request
self.language=language
self.language_strategy=Language.match(language)
@view_config(request_method='GET')
defshow_local_entries(self):
return{'ok':True}
Consistency check
Adding a new language
classLanguage(adt):
ENGLISH='en'
GERMAN='de'
SPANISH='es'
ConfigurationError:
Casedb:articlesof<class'project.articles.types.Language'>
isnotexhaustive.Hereisthevariantthatisnotmatched:SPANISH
#Wehavetoregisterthiscase
@Language.SPANISH('db:articles')
classSpanishArticle(IRegionalArticle):
pass
Consistency check
Using undefined variant
fromrhetoric.adtimportadt
classLanguage(adt):
ENGLISH='en'
GERMAN='de'
inline_matcher=Language.inline_match(
ENGLISH=lambda:EnglishArticle.objects.all(),
GERMAN=lambda:GermanArticle.objects.all(),
SPANISH=lambda:SpanishArticle.objects.all()
)
PatternError:VariantSPANISHdoesnotbelong
tothetype<class'project.articles.types.Language'>
Consistency check
Guard boundaries
from.typesimportLanguage
defincludeme(config):
RULES={'language':Language}
config.add_route('articles.regional.index','/articles/{language}',RULES
)
/articles/{language} => ^articles/(?P<language>(?:de|en))$
classLanguage(adt):
ENGLISH='en'
GERMAN='de'
SPANISH='es'
/articles/{language} => ^articles/(?P<language>(?:de|en|es))$
Use case
Strategy map
classArticlesHandler(object):
def__init__(self,request,language):
self.request=request
self.language=language
self.language_strategy=Language.match(language)
GET/articles/de
{'db:articles':<class'project.articles.models.GermanArticle'>}
GET/articles/en
{'db:articles':<class'project.articles.models.EnglishArticle'>}
Beyond today's topic
Things worth mentioning
github.com/lihaoyi/macropy
github.com/benanhalt/PyAlgebraicDataTypes (cons ADT)
束Say What You Mean損 talk by Ryan Kelly
Thank you
Q & A
Credits
talk by Yaron Minsky束Caml Trading損
Rhetoric Project
Venusian Project

More Related Content

Declarative Programming & Algebraic Data Types from Django's perspective