declarative programming & algebraic data types from django's perspective

35
Declarative Programming & Algebraic Data Types * Maxim Avanov maximavanov.com * from Django's perspective 19th Moscow Django Meetup

Upload: maxim-avanov

Post on 23-Jun-2015

177 views

Category:

Technology


2 download

TRANSCRIPT

Page 1: Declarative Programming & Algebraic Data Types from Django's perspective

Declarative Programming&

Algebraic Data Types *

Maxim Avanovmaximavanov.com

* from Django's perspective

19th Moscow Django Meetup

Page 2: Declarative Programming & Algebraic Data Types from Django's perspective

Our Goal1. «Outsource» boilerplate (i.e. concentrate on important).2. Check as much as possible, and as soon as possible.3. Component coherence.

Page 3: Declarative Programming & Algebraic Data Types from Django's perspective

1. A story of How & WhatConcentrate on important

Page 4: Declarative Programming & Algebraic Data Types from Django's perspective

How vs. Whatdef handle_article_form(request): if request.method == 'POST': form = ArticleForm(request.POST) if form.is_valid(): save_article(form.cleaned_data) return HttpResponseRedirect('/success/') else: form = ArticleForm()

return render(request, 'article_form.html', {'form': form})

Page 5: Declarative Programming & Algebraic Data Types from Django's perspective

Howif request.method == 'POST': form = ArticleForm(request.POST) if form.is_valid(): # ...else: # ...

What# case 1save_article(form.cleaned_data)return HttpResponseRedirect('/success/')

# case 2form = ArticleForm()return render(request, 'article_form.html', {'form': form})

Page 6: Declarative Programming & Algebraic Data Types from Django's perspective

A few things to worry aboutdef handle_article_form(request): if request.method == 'POST': form = ArticleForm(request.POST) if form.is_valid(): save_article(form.cleaned_data) return HttpResponseRedirect('/success/') else: form = ArticleForm()

return render(request, 'article_form.html', {'form': form})

«What» is obscured by «How»redundant detailsSingle Responsibility principle is violated

Page 7: Declarative Programming & Algebraic Data Types from Django's perspective

If we could get rid of How's once and for all,would we miss them?

Page 8: Declarative Programming & Algebraic Data Types from Django's perspective

Constraint programming

Page 9: Declarative Programming & Algebraic Data Types from Django's perspective

Tribute to Pyramidfrom rhetoric import view_config, view_defaults

@view_defaults(route_name='articles', renderer='article_form.html')class ArticlesHandler(object): def __init__(self, request): self.request = request

@view_config(request_method='GET') def article_form(self): form = ArticleForm() return {'form': form}

@view_config(request_method='POST', validate_form=ArticleForm) def save_article(self): save_article(self.request.form.cleaned_data) return HttpResponseRedirect('/success/')

@view_config(request_method='POST') def invalid_article_form(self): return {'form': self.request.form}

Page 10: Declarative Programming & Algebraic Data Types from Django's perspective

What it actually means# We have an Articles Handler: ArticlesHandler# It renders a template: article_form.html# A user shall be able to add new entries: article_form()# If we submit valid ArticleForm: save_article()# If we submit invalid ArticleForm: invalid_article_form()

class ArticlesHandler(object): def __init__(self, request): self.request = request

def article_form(self): form = ArticleForm() return {'form': form}

def save_article(self): save_article(self.request.form.cleaned_data) return HttpResponseRedirect('/success/')

def invalid_article_form(self): return {'form': self.request.form}

Page 11: Declarative Programming & Algebraic Data Types from Django's perspective

...or in other words---view: ArticlesHandlerGET: article_formPOST: - validate: articles.ArticleForm view: save_article - view: invalid_article_form

class ArticlesHandler(object): def __init__(self, request): self.request = request

def article_form(self): form = ArticleForm() return {'form': form}

def save_article(self): save_article(self.request.form.cleaned_data) return HttpResponseRedirect('/success/')

def invalid_article_form(self): return {'form': self.request.form}

Page 12: Declarative Programming & Algebraic Data Types from Django's perspective

A couple of other examples

Page 13: Declarative Programming & Algebraic Data Types from Django's perspective

Different ways to do the same thing@view_defaults(route_name='authentication', renderer='auth_form.html')class AuthenticationHandler(object):

@view_config(request_method='POST', validate_form=EmailAuthForm) def auth_with_email(self): # ...

@view_config(request_method='POST', validate_form=SMSAuthForm) def auth_with_sms(self): # ...

@view_config(request_method='POST', validate_form=LoginAuthForm) def auth_with_login(self): # ...

@view_config(request_method='POST') def on_invalid_form(self): # ...

Page 14: Declarative Programming & Algebraic Data Types from Django's perspective

API versioningconfig.add_route('api.workflows', '/api/workflows')

@view_defaults(route_name='api.workflows', api_version='<2.0')class WorkflowsAPIv1(object): # ...

@view_defaults(route_name='api.workflows', api_version='>=2.0')class WorkflowsAPIv2(object): # ...

@view_defaults(route_name='api.workflows', renderer='json')class WorkflowsAPI(object):

@view_config(request_method='POST', api_version='<2.0') def create_new_workflow_v1(self): # ...

@view_config(request_method='POST', api_version='>=2.0') def create_new_workflow_v2(self): # ...

Page 15: Declarative Programming & Algebraic Data Types from Django's perspective

Algebraic Data Types

Page 16: Declarative Programming & Algebraic Data Types from Django's perspective

OCaml ADT exampleWatch a «Caml Trading» talk by Yaron Minsky at

http://youtu.be/hKcOkWzj0_s?t=31m6s

Page 17: Declarative Programming & Algebraic Data Types from Django's perspective

OCaml ADT exampletype order = { id: int; price: float; size: int; }type cancel = { xid: int; }

type instruction = | Order of order | Cancel of cancel

let filter_by_oid instructions oid = List.filter instructions (fun x -> match x with | Order o -> o.id = oid | Cancel c -> c.xid = oid)

Page 18: Declarative Programming & Algebraic Data Types from Django's perspective

OCaml ADT exampletype order = { id: int; price: float; size: int; }type cancel = { xid: int; }

type cancel_replace = { xr_id: int; new_price: float; new_size: int; }

type instruction = | Order of order | Cancel of cancel | Cancel_replace of cancel_replace

let filter_by_oid instructions oid = List.filter instructions (fun x -> match x with | Order o -> o.id = oid | Cancel c -> c.xid = oid)

Warning P: This pattern-matching is not exhaustiveHere is an example of a value that is not matched...

Page 19: Declarative Programming & Algebraic Data Types from Django's perspective

2. Trying to reproduceCheck as much as possible, and as soon as possible

Page 20: Declarative Programming & Algebraic Data Types from Django's perspective

Python ADT *

* kind of

Page 21: Declarative Programming & Algebraic Data Types from Django's perspective

Python ADT example

Models (product types)

from django.db import models

class Order(models.Model): tid = models.IntegerField() price = models.DecimalField(max_digits=16, decimal_places=4) size = models.IntegerField()

class Cancel(models.Model): xtid = models.IntegerField()

class CancelReplace(models.Model): xr_tid = models.IntegerField() new_price = models.DecimalField(max_digits=16, decimal_places=4) new_size = models.IntegerField()

Page 22: Declarative Programming & Algebraic Data Types from Django's perspective

Python ADT example

Smart Enums (union types)

from rhetoric.adt import adtfrom .models import Order, Cancel, CancelReplace

class Instruction(adt): ORDER = Order CANCEL = Cancel CANCEL_REPLACE = CancelReplace

Page 23: Declarative Programming & Algebraic Data Types from Django's perspective

Python ADT example

Cases (match statement)

from .types import Instruction

@Instruction.ORDER('filter_by_oid')def filter_order_by_oid(order, oid): return order.tid == oid

@Instruction.CANCEL('filter_by_oid')def filter_cancel_by_oid(cancel, oid): return cancel.xtid == oid

@Instruction.CANCEL_REPLACE('filter_by_oid')def filter_cancel_replace_by_oid(cr, oid): return cr.xr_tid == oid

def filter_by_oid(instructions, oid): return list(filter( lambda i: Instruction.match(i)['filter_by_oid'](i, oid), instructions))

Page 24: Declarative Programming & Algebraic Data Types from Django's perspective

Python ADT example

Cases (2)

from .types import Instruction

inline_matcher = Instruction.inline_match( ORDER = (lambda o, oid: o.tid == oid), CANCEL = (lambda c, oid: c.xtid == oid), CANCEL_REPLACE = (lambda cr, oid: cr.xr_tid == oid))

def filter_by_oid_alt(instructions, oid): return list(filter(lambda i: inline_matcher(i)(i, oid), instructions))

Page 25: Declarative Programming & Algebraic Data Types from Django's perspective

3. Use case: multilingual contentComponent coherence

Page 26: Declarative Programming & Algebraic Data Types from Django's perspective

Use case

Define ADT

from rhetoric.adt import adt

class Language(adt): ENGLISH = 'en' GERMAN = 'de'

Page 27: Declarative Programming & Algebraic Data Types from Django's perspective

Use case

Register Models

from django.db import modelsfrom .types import Language

class IRegionalArticle(models.Model): class Meta: abstract = True title = models.CharField(max_length=140, default='') text = models.TextField(max_length=65536, default='')

@Language.ENGLISH('db:articles')class EnglishArticle(IRegionalArticle): pass

@Language.GERMAN('db:articles')class GermanArticle(IRegionalArticle): pass

Page 28: Declarative Programming & Algebraic Data Types from Django's perspective

Use case

Process requests

from rhetoric.view import view_config, view_defaultsfrom .types import Language

@view_defaults(route_name='articles.regional.index', renderer='json')class ArticlesHandler(object): def __init__(self, request, language): self.request = request self.language = language self.language_strategy = Language.match(language)

@view_config(request_method='GET') def show_local_entries(self): return {'ok': True}

Page 29: Declarative Programming & Algebraic Data Types from Django's perspective

Consistency check

Adding a new language

class Language(adt): ENGLISH = 'en' GERMAN = 'de' SPANISH = 'es'

ConfigurationError: Case db:articles of <class 'project.articles.types.Language'> is not exhaustive. Here is the variant that is not matched: SPANISH

# We have to register this [email protected]('db:articles')class SpanishArticle(IRegionalArticle): pass

Page 30: Declarative Programming & Algebraic Data Types from Django's perspective

Consistency check

Using undefined variant

from rhetoric.adt import adt

class Language(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: Variant SPANISH does not belong to the type <class 'project.articles.types.Language'>

Page 31: Declarative Programming & Algebraic Data Types from Django's perspective

Consistency check

Guard boundaries

from .types import Language

def includeme(config): RULES = {'language': Language} config.add_route('articles.regional.index', '/articles/{language}', RULES)

/articles/{language} => ̂articles/(?P<language>(?:de|en))$

class Language(adt): ENGLISH = 'en' GERMAN = 'de' SPANISH = 'es'

/articles/{language} => ̂articles/(?P<language>(?:de|en|es))$

Page 32: Declarative Programming & Algebraic Data Types from Django's perspective

Use case

Strategy map

class ArticlesHandler(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'>}

Page 33: Declarative Programming & Algebraic Data Types from Django's perspective

Beyond today's topicThings worth mentioning

github.com/lihaoyi/macropygithub.com/benanhalt/PyAlgebraicDataTypes (cons ADT)«Say What You Mean» talk by Ryan Kelly

Page 34: Declarative Programming & Algebraic Data Types from Django's perspective

Thank you

Q & A

Page 35: Declarative Programming & Algebraic Data Types from Django's perspective

Credits talk by Yaron Minsky«Caml Trading»

Rhetoric ProjectVenusian Project