Upload
others
View
2
Download
0
Embed Size (px)
Citation preview
Func%onal)programming)in)Swi1
Wouter'Swierstra
BOB#Konf
BOB#Konf 1
WWDCJune%2014
BOB#Konf 2
BOB#Konf 3
What%kind%of%language%is%Swi3?
BOB#Konf 4
It’s%like%Objec.ve0C,%without%the%C.—"Craig"Federighi,"WWDC
BOB#Konf 5
"Objec've)C+without+the+C"+implies+something+subtrac've,+but+Swi<+drama'cally+expands+the+design+space+through+the+introduc'on+of+generics+and+func'onal+programming+concepts.
–"Chris"La*ner,"Apple"Developer"Forums
BOB#Konf 6
What%is%func,onal%programming?
BOB#Konf 7
Maps%and%filters
Many%people%describe%func2onal%programming%as%being%about%map,%filter%and%reduce:
[1,2,3].map({x in x + 1})
These%are%examples%of%func1ons%drawn%from%FP.
But$this$is$like$saying$object$oriented$programming$is$about$Shapes$and$Animals.
BOB#Konf 8
Characteris*cs+of+FP
• Modularity
• Effec0ve2usage2of2types
• Careful2treatment2of2state2and2effects
BOB#Konf 9
What%you%will%learn
The$aim$of$this$talk$is$not$to$teach$you$Swi4...
..."or"the"frameworks"and"IDE"for"iOS"and"OS"X"development.
But$rather$showcase$how$you$can$use$some$of$the$ideas$from$func3onal$programming$in$your$projects.
BOB#Konf 10
Core%Image
BOB#Konf 11
BOB#Konf 12
San$Francisco
BOB#Konf 13
The$challenge
The$Core$Image$API$is$a$bit$clunky.
CIFilter *hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"];[hueAdjust setDefaults];[hueAdjust setValue: myCIImage forKey: kCIInputImageKey];[hueAdjust setValue: @2.094f forKey: kCIInputAngleKey];
The$resul)ng$image$a/er$running$the$filter$can$be$retrieved$from$the$kCIOutputImageKey.
BOB#Konf 14
The$challenge
The$Core$Image$API$has$some$drawbacks:
• It's&easy&to&forget&to&set&some¶meters&(or&set&the&wrong¶meters)&–&causing&a&run9:me&crash;
• Not&type&safe&–&you&can&set&the&wrong&type&of&value&for&some&key,&which&again&causes&a&run9:me&crash;
• Not&modular&–&there's&no&easy&way&to&compose&two&filters.
BOB#Konf 15
A"func'onal"solu'on
typealias Filter = CIImage -> CIImage
A"filter"is"a"func/on"that"transforms"an"image.
BOB#Konf 16
A"trivial"filter
func noFilter() -> Filter { return {image in return image}}
This%filter%does%nothing,%and%returns%the%input%image.
BOB#Konf 17
An#example#filterfunc blur(radius: Double) -> Filter { return {image in let parameters : Parameters = [kCIInputRadiusKey: radius, kCIInputImageKey: image] // Here we're calling a convenience initializer // that sets certain defaults let blurFilter = CIFilter(name:"CIGaussianBlur", parameters:parameters) return blurFilter.outputImage }}
BOB#Konf 18
Calling'this'filter
let url = NSURL(string: "http://tinyurl.com/sfswift")let image : CIImage = CIImage(contentsOfURL: url)
let blurBy5 : Filter = blur(5)let blurred : CIImage = blurBy5(image)
BOB#Konf 19
Other&filtersfunc compositeSourceOver(overlay: CIImage) -> Filter {...}
func colorGenerator(color: NSColor) -> Filter {...}
func colorOverlay(color: NSColor) -> Filter {...}
BOB#Konf 20
Filtering)more)than)once
let img : CIImage = ...let blurRadius = 5.0let overlayColor = NSColor.whiteColor().colorWithAlphaComponent(0.2)let blurredImage = blur(blurRadius)(image)let overlaidImage = colorOverlay(overlayColor)(blurredImage)
BOB#Konf 21
How$can$we$compose$filters?
BOB#Konf 22
Composing)filters)–)func3on)composi3on
func composeFilters(filter1: Filter, filter2: Filter) -> Filter { return {img in filter2(filter1(img)) }}
let img = ...let compositeFilter = compose(blur(blurRadius), colorOverlay(overlayColor))
let filteredImg = compositeFilter(img)
BOB#Konf 23
A"composi)on"operatorinfix operator >>> { associativity left }
func >>> (filter1: Filter, filter2: Filter) -> Filter { return {img in filter2(filter1(img))}}
let myFilter = blur(blurRadius) >>> colorOverlay(overlayColor)
We#can#now#chain#together#filters,#similarly#to#Unix#pipes.
BOB#Konf 24
Taking'stock
We#have#a#wrapper#around#a#fragment#of#Core#Image#that#is#both#type%safe#and#modular.
Using&higher*order&func0ons&(similar&to&Objec0ve*C's&blocks)...
..."but"there"are"alterna,ve"defini,ons"that"have"the"same"composi,onal"behaviour,"that"are"en,rely"first8order.
BOB#Konf 25
Enumera(ons
BOB#Konf 26
Enumera(ons+in+Objec(ve+C
Enumera(ons+are+thin+wrapper+around+a+collec(on+of+integer+constants:
enum NSStringEncoding { NSASCIIStringEncoding = 1, NSNEXTSTEPStringEncoding = 2, NSJapaneseEUCStringEncoding = 3, NSUTF8StringEncoding = 4,// ...}
BOB#Konf 27
Enumera(ons+in+Objec(ve+C
Why$should$such$expressions$make$sense?
if (NSASCIIStringEncoding + NSNEXTSTEPStringEncoding == NSJapaneseEUCStringEncoding) {...}
BOB#Konf 28
Enumera(ons+in+Swi/
In#Swi'#on#the#other#hand,#enumera2ons:
• introduce+a+new+type,+separate+from+the+underlying+integers;
• may+have+associated+values;
• can+be+decomposed+by+pa:ern+matching.
BOB#Konf 29
Reading(a(file(–(Obj(C
The$type$of$the$NSString$ini*alizer$isn't$helpful.$
+ (instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
To#check#for#errors,#do#I#inspect#the#error#or#the#return#value?
BOB#Konf 30
Reading(a(file(–(Swi.func readFile(path: String, encoding: NSStringEncoding) -> String? { var maybeError: NSError? = nil return NSString(contentsOfFile: path, encoding: encoding, error: &maybeError)}
The$type$is$telling$us$more,$but$we$can't$get$our$hands$on$the$NSError$when$the$func9on$fails.
BOB#Konf 31
Enumera(ons
enum Result { case Success(String) case Failure(NSError)}
A"value"of"type"Result"is"tagged"as"being"either:
• Success"–"in"which"case"we"have"the"file"contents;
• Failure"–"in"which"case"we"have"an"NSError.
BOB#Konf 32
readFile(revisitedfunc readFile(path: String, encoding: NSStringEncoding) -> Result { var maybeError: NSError? let maybeString: String? = NSString(contentsOfFile: path, encoding: encoding, error: &maybeError) if let string = maybeString { return Result.Success(string) } else { return Result.Failure(maybeError!) }}
BOB#Konf 33
Reading(a(file
switch readFile("README.md", NSASCIIStringEncoding) { case let Result.Success(contents): // Process file contents ... case let Result.Failure(error): // Handle error ...}
BOB#Konf 34
Diagrams(in(Swi,
BOB#Konf 35
Diagrams(in(Objec/ve(CNSColor.blueColor().setFill()CGContextFillRect(context, CGRectMake(0.0, 37.5, 75.0, 75.0))NSColor.redColor().setFill()CGContextFillRect(context, CGRectMake(75.0, 0.0, 150.0, 150.0))NSColor.greenColor().setFill()CGContextFillEllipseInRect(context, CGRectMake(225.0, 37.5, 75.0, 75.0))
BOB#Konf 36
Diagrams(in(Objec/ve(C
But$what$if$I$want$to$draw$this:
Instead(of(this:
BOB#Konf 37
Diagrams(in(Objec/ve(C
The$drawing$commands$are$non0composi2onal:
• They&have&hard+coded&coordinates;
• They&focus&on&how&things&should&be&drawn&rather&than&what&should&be&drawn;
• Any&changes&to&a&sub+drawing&require&rewri;ng&the&complete&code.
BOB#Konf 38
A"func'onal"solu'on...
Instead(of(drawing(commands(directly,(we'll(design(a(domain'specific'language(for(diagram(descrip6ons.
1. Define'an'enumera,on'that'describes'diagrams
2. 'Interpret''this'descrip,on'using'a'Core'Graphics
BOB#Konf 39
Intended&solu+onlet blueSquare = square(side: 1).fill(NSColor.blueColor())let redSquare = square(side: 2).fill(NSColor.redColor())let greenCircle = circle(radius: 1).fill(NSColor.greenColor())let example1 = blueSquare ||| redSquare ||| greenCircle
BOB#Konf 40
Intended&solu+on
Adding&new&shapes&is&easy:
let cyanCircle = circle(radius: 1).fill(NSColor.cyanColor())let example2 = blueSquare ||| cyanCircle ||| redSquare ||| greenCircle
This%solu)on%is%composi'onal
BOB#Konf 41
Diagrams(in(Swi,1
enum Primitive { case Ellipse case Rectangle case Text(String)}
enum Diagram { case Prim(CGSize, Primitive) case Beside(Diagram, Diagram) case Below(Diagram, Diagram) case Attributed(Attribute, Diagram) case Align(Vector2D, Diagram)}
1"Recursive"enumera.ons"need"a"workaround
BOB#Konf 42
Example:)compu-ng)the)sizeextension Diagram { var size: CGSize { switch self { case let .Prim(size, _): return size case let .Attributed(_, x): return x.size case let .Beside(l, r): let sizeL = l.size let sizeR = r.size return CGSizeMake(sizeL.width + sizeR.width, max(sizeL.height, sizeR.height)) ...
BOB#Konf 43
Drawing(a(diagramfunc draw(context: CGContextRef, bounds: CGRect, diagram: Diagram) { switch diagram { case let .Prim(size, .Ellipse): let frame = fit(defaultAlign, size, bounds) CGContextFillEllipseInRect(context, frame)
// And similar cases for drawing text and squares
BOB#Konf 44
Drawing(composite(diagramsfunc draw(context: CGContextRef, bounds: CGRect, diagram: Diagram) { switch diagram { ... case let .Beside(left, right): let (lFrame, rFrame) = splitHorizontal(bounds, left.size/diagram.size) draw(context, lFrame, left) draw(context, rFrame, right)
A"few"more"cases"for"ver-cal"composi-on,"alignment,"etc.
BOB#Konf 45
Building(a(more(complete(library
On#top#of#this#we#can#define#combinators#to#make#it#easier#to#define#complex#diagrams:
func square(side: CGFloat) -> Diagram { return rect(width: side, height: side) }
infix operator ||| { associativity left }func ||| (l: Diagram, r: Diagram) -> Diagram { return Diagram.Beside(l,r)}
infix operator --- { associativity left }func --- (l: Diagram, r: Diagram) -> Diagram { return Diagram.Below(l,r)}
BOB#Konf 46
Adding&a(ributes&or&alignmentextension Diagram { func fill(color: NSColor) -> Diagram { return Diagram.Attributed(Attribute.FillColor(color), self) }
func alignTop() -> Diagram { return Diagram.Align(Vector2D(x: 0.5, y: 1), self) }}
So#we#can#now#write:
let redSquare = square(side: 2).fill(NSColor.redColor())let greenCircle = circle(radius: 1).fill(NSColor.greenColor())let example1 = greenCircle.alignTop() ||| redSquare
BOB#Konf 47
Combining(lists(of(diagrams
let empty: Diagram = rect(width: 0, height: 0)
func hcat(diagrams: [Diagram]) -> Diagram { return diagrams.reduce(empty, |||)}
BOB#Konf 48
Example:)visualizing)dic3onarieslet cities = ["Moscow": 10.56, "Shanghai": 14.01, "Istanbul": 13.3, "Berlin": 3.43, "New York": 8.33]
Moscow Shanghai Istanbul Berlin New York
BOB#Konf 49
Genera&ng(bar(graphsfunc barGraph(input: [(String, Double)]) -> Diagram { let normalizedValues : [CGFloat] = normalize(input) let bars = hcat(normalizedValues.map { x in rect(width: 1, height: 3 * x) .fill(NSColor.blackColor()) .alignBottom() }) let labels = hcat(input.map { x in text(width: 1, height: 0.3, text: x.0) .alignTop() }) return bars --- labels}
BOB#Konf 50
Diagrams• A#composi*onal#language#for#defining#simple#diagrams
• Easy#to#extend#with#new#combinators
• Separates#the#what#from#the#how
• The#same#techniques#can#be#used#in#other#programming#languages,#but#they#feel#more#natural#in#Swi@.
BOB#Konf 51
Things'I'haven't'talked'about• Generics)&)protocols
• Reference)types)versus)value)types
• Sequences)&)generators
• QuickCheck)&)tes<ng)func<onal)code
• Currying,)parser)combinators,)typeAlevel)programming...
BOB#Konf 52
The$future$of$Swi-
Apple%is%star+ng%to%ac+vely%push%Swi6...
Swi$%offers%developers%both%a%great%pla$orm%and%a%great%language
BOB#Konf 53
Learning(more• Lots&of&Apple&documenta2on
• Swi6&tutorial&today
• Func2onal&Swi6&Conference
• obj.io&–&Issue&16
• Summer&School&on&Applied&Func2onal&Programming&in&Utrecht.
BOB#Konf 54
Ques%ons?
BOB#Konf 55