Lambda!? Hvad siger manden?

Oprindeligt havde dette indlæg en lidt anden indledning. Efter jeg publicerede det, fik jeg dog noget konstruktiv kritik og efter lidt overvejelser, besluttede jeg mig for at skrive indledningen om.

Ideen til dette indlæg kom oprindeligt fra et vidensdelingsmøde på arbejdet, hvor en kollega udbrød: “lambda’er?! Jeg kan svagt huske, at jeg har hørt om det på studiet, men hvad er det nu lige det er?”. Da min computer sad i projektoreren, stykkede jeg hurtigt et lille eksempel sammen. Jeg var dog stærkt utilfreds med eksemplet, så jeg arbejdede videre med det.

Hvorfor er denne anekdote relevant? Jo, java 8 er lige på trapperne. Det har Jeppe skrevet og talt om lidt om tidligere. På sidste års GOTO i Aarhus, nævnte Brian Goetz, at denne release handler meget om, at udvide java med functional programming, uden at sige “the f-word”. Når java nu udvides med functional programming, kommer det ud til en bredere skare end tidligere. Lambdaudtryk er centralt for forståelsen af functional programming og sidder sidder man med fornemmelsen af at have hørt om lambda’er, men ikke rigtig kan huske hvad det er, så kan et eksempel måske hjælpe.

I eksemplet herunder vil jeg trække på entropibegrebet fra informationsteori. Uden at ville gå dybere ind i en diskusion af begrebet, vil jeg nøjes med at sige, at entropien i informationsteori lidt svarer til, at bestemme hvor mange bits der skal til, for at repræsentere informationsmængden i et udfaldsrum udfra nogle sansynligheder for hvert udfald. Haves 2 udfald, A og B med lige stor sansynlighed, er entropien 1. Det vil sige, at udfaldsrummet kan beskrives med 1 bit. Har A 100% sandsynlighed og B 0% sandsynlighed er entropien 0, da man helt kan se bort fra informationen. Det er jo altid det samme. Eksemplerne er skrevet i python, da jeg synes pythons syntax omkring lambdaudtryk er enkelt og tydelig, hvilket gør det enklere at forklare og gennemskue hvad der sker.

Entropien beregnes med følgende formel:
Beregning af entropi. Kilde: Wikipedia

 

I python kan dette skrives som

#!/usr/bin/env python

import math

def entropi1(data):
	sum = 0
	for p in data:
		sum = sum + p*math.log(p, 2)
	return -sum

data = [0.5, 0.5]
print('entropi1: %0.2f' % entropi1(data))

I eksemplet er data angivet som 2 udfald med lige stor sandsynlighed, og output fra programmet er:

entropi1: 1.00

Nu skriver jeg lige linie 8 i ovenstående kode om, og lader beregningen foregå i en funktion for sig selv.

def beregn(s, p):
	return s + p*math.log(p, 2)

def entropi2(data):
	s = 0
	for p in data:
		s = beregn(s, p)
	return -s

print('entropi2: %0.2f' % entropi2(data))
entropi2: 1.00

Det gør jeg fordi, der allerede findes en funktion, reduce, som kan gøre nøjagtig det, som entropi2() gør, nemlig at iterere over en liste og foretage en beregning for hvert element og addere det til summen af beregningerne for de forrige elementer. Alt hvad den skal have, er en reference til en funktion, der tager 2 argumenter: resultatet af den forrige beregning samt værdien af det næste element i listen. Præcis som beregn(s, p).

def beregn(s, p):
	return s + p*math.log(p, 2)

def entropi3(data):
	return -reduce(beregn, data, 0)


print('entropi3: %0.2f' % entropi3(data))
entropi3: 1.00

Nu ville det jo være super cool, om man, i stedet for at give en reference til en funktion, kunne give noget kode med som argument til reduce og det kan man rent faktisk. Det er her lambdaudtryk, eller closures som de også kaldes, kommer ind i billedet. Lambda’er laves med nøgleordet lambda efterfulgt af en liste af argumenter, så et kolon efterfulgt af et udtryk, der opererer på argementerne. Funktionen sum(s, p) ser som lambda således ud:

sum = lambda s, p: s + p*math.log(p, 2)

Man kan sige at labmdaudtrykket er en anonym funktion, så lad os benytte lambdaen direkte i kaldet af reduce

def entropi4(data):
	return -reduce(lambda s, p: s + p*math.log(p, 2), data, 0)

print('entropi4: %0.2f' % entropi4(data))
entropi4: 1.00

Men et er at have lambdaer som data i kaldet til sine funktioner, noget andet er, at funktioner også kan returnere lambdaer. Funktionen reduce tager både en funktion og noget data som funktionen skal operere på. Hvad hvis man lavede en funktion, reducer, der som input tager en funktion som reduce skal bruge og returnerer en ny funktion, som kan tage 1 argment, data, som er det andet argument reduce skal bruge.

def reducer(func):
	return lambda data: reduce(func, data, 0)

Den kan kan nu bruges til at bygge nye funktioner runtime. Nedenfor laves både en entropifunktion og en funktion check, det summerer inddata, så vi kan se om den samlede sandsynlighed i inddata nu også er 1.

entropi6 = reducer( lambda s, p: s + p * math.log(p, 2) )
checksum = reducer( lambda s, p: s + p )

print('entropi6: %0.2f' % -entropi6(data))
print('checksum: %0.2f' % checksum(data))
entropi6: 1.00
checksum: 1.00

et voilà: lambda’er

2 comments for “Lambda!? Hvad siger manden?

    • Jo, men man kommer ikke uden om reduce i længden. Den eneste grund til at du, i dit eksempel, ikke rammer reduce, er fordi nogen allerede har implementeret sum for dig:

      def sum(liste):
          return reduce(lambda a,b: a+b, liste, 0)
      

      i clojure findes f.eks. ingen indbygget funktion sum, så der render man ud i, at skulle bruge reduce, hvis man vil prøve ovenstående eksempler.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *