Constraint-Based Type-Directed Program Synthesis

Total Page:16

File Type:pdf, Size:1020Kb

Constraint-Based Type-Directed Program Synthesis Constraint-Based Type-Directed Program Synthesis Peter-Michael Osera Department of Computer Science Grinnell College United States of America [email protected] Abstract 2. The result of pattern matching on the second argument a We explore an approach to type-directed program synthe- (in the Just case, its argument will have type ). sis rooted in constraint-based type inference techniques. By These restrictions highly constrain the set of valid programs doing this, we aim to more efficiently synthesize polymor- that will typecheck, giving the impression that the function phic code while also tackling advanced typing features such writes itself as long as the programmer can navigate the type as GADTs that build upon polymorphism. Along the way, system appropriately. To do this, they must systematically we also present an implementation of these techniques in check the context to see what program elements are rele- Scythe, a prototype live, type-directed programming tool vant to their final goal and try to put them together into for the Haskell programming language and reflect on our a complete program. However, such navigation is usually initial experience with the tool. mechanical and tedious in nature. It would be preferable if a CCS Concepts • Software and its engineering → Se- tool automated some or all of this type-directed development mantics; Automatic programming; • Theory of compu- process. tation → Logic and verification. Such luxuries are part of the promise of type-directed pro- gram synthesis tools. Program synthesis is the automatic Keywords Functional Programming, Program Synthesis, generation of programs from specification. In this particular Type Inference, Type Theory case, the specification of our program is its rich type cou- ACM Reference Format: pled with auxiliary information, e.g., concrete examples of Peter-Michael Osera. 2019. Constraint-Based Type-Directed Pro- intended behavior. gram Synthesis. In Proceedings of the 4th ACM SIGPLAN Interna- tional Workshop on Type-Driven Development (TyDe ’19), August 1.1 From Typechecking to Program Synthesis 18, 2019, Berlin, Germany. ACM, New York, NY, USA, 13 pages. Type-directed synthesis techniques search the space of pos- hps://doi.org/10.1145/3331554.3342608 sible programs primarily through a reinterpretation of the programming language’s type system [6, 19, 22]. Tradition- 1 Introduction ally, type systems are specified by defining a relation between Functional programmers frequently comment how richly- a context, expression, and type, e.g., Γ ⊢ e : τ declares that e typed functional programs just “write themselves”.1 For ex- has type τ under context Γ. From this specification, we would ample, consider writing down a function that obeys the type: like to extract a typechecking algorithm for the language. f :: a -> Maybe a -> a However, because relations do not distinguish between in- Because the return type of the function, a, is polymorphic puts and outputs, it is sometimes not clear how to extract we can only produce a value from two sources: such an algorithm. Bi-directional typechecking systems [21] alleviate these concerns by making it explicit which compo- 1. The first argument to the function (of type a). nents of the system are inputs and outputs of the system, 1Once you get over the complexity of the types! typically by distinguishing the cases where we check that a term has a type from the cases where we infer that a term Permission to make digital or hard copies of all or part of this work for has a type. In the former case, the type acts as an input to personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that the system where in the latter case, it is an output. copies bear this notice and the full citation on the first page. Copyrights In both cases, the term being typechecked serves as an for components of this work owned by others than the author(s) must input to the typechecking algorithm. However, with type- be honored. Abstracting with credit is permitted. To copy otherwise, or directed program synthesis, we instead view the term as republish, to post on servers or to redistribute to lists, requires prior specific an output and the type as an input. For example, consider permission and/or a fee. Request permissions from [email protected]. TyDe ’19, August 18, 2019, Berlin, Germany the standard function application rule found in most type © 2019 Copyright held by the owner/author(s). Publication rights licensed systems: to ACM. Γ ⊢ e1 : τ1 → τ2 Γ ⊢ e2 : τ1 ACM ISBN 978-1-4503-6815-5/19/08...$15.00 hps://doi.org/10.1145/3331554.3342608 Γ ⊢ e1 e2 : τ2 TyDe ’19, August 18, 2019, Berlin, Germany Peter-Michael Osera While we can observe that the function application should be there is nothing directly constraining the type variable typed at the result type of the function e1, it isn’t clear which a. parts of the relations are inputs and outputs and the order Current systems that handle polymorphic synthesis in which the checks ought to be carried out. A bidirectional simply enumerate and explore all the possible types interpretation of this rule makes the inputs and outputs that can be created from the context. However, this explicit: may lead to an excessive exploration of type combi- nations that do not work, e.g., choosing a to be Int Γ Γ ⊢ e1 ⇒ τ1 → τ2 ⊢ e2 ⇐ τ1 but not having any functions of type Int -> Bool Γ ⊢ e1 e2 ⇒ τ1 → τ2 available in the context. Furthermore, it may lead to repeated checking of terms that are, themselves, poly- Γ Here, the relation ⊢ e1 ⇒ τ1 → τ2 means that we infer that morphic, e.g., the empty list [] :: [a] for any type a. Γ the type of e1 is τ1 → τ2. The relation ⊢ e2 ⇐ τ1 means We would like to develop a system that systematically that we check that the type of e2 is indeed τ1. With this, the searches the space of polymorphic instantiations while procedure for type checking a function application is clear: minimizing the work done as much as possible. infer a function type τ1 → τ2 for e1 and then check that e2 • Reasoning about richer types: polymorphic types has that input type τ1; the type of the overall application is are relatively easy to handle in an ad hoc fashion. How- then inferred to be the output type τ2. ever, polymorphic types form the basis for a variety of With type-directed synthesis, we turn typechecking into advanced type features such as generalized algebraic term generation by reinterpreting the inputs and outputs of datatypes (GADTs) and typeclasses that are commonly the typechecking relation. used in advanced functional programming languages. Rather than developing ad hoc solutions for all these Γ ⊢ τ1 → τ2 ⇒ e1 Γ ⊢ τ1 ⇒ e2 related features, it would be useful to have a single Γ ⊢ τ2 ⇒ e1 e2 framework for tackling them all at once. The relation Γ ⊢ τ ⇒ e asserts that whenever we have a goal type τ we can generate a term e of that type. Now our term 1.3 Outline generation rule for function application says that whenever In this paper we present a solution to the problems described we have a goal typeτ2, we can generate a function application above: a constraint-based approach to type-directed pro- e1 e2 where the output of the function type agrees with the gram synthesis. Constraint-based typing is used primarily to goal. We can apply this pattern to the other rules of the specify type inference systems which generate constraints type system to obtain a complete term generation system between types in the program and then solves those con- for a language. We can further augment the system with straints to discover the types of unknown type variables. In type-directed example decomposition [6, 19] or richer types the spirit of type-directed program synthesis, we flip the such as refinements [22] to obtain true program synthesis inputs and outputs of the constraint-based typing system systems. to arrive at a synthesis system that tracks type constraints This type-theoretic interpretation of program synthesis throughout the synthesis process. This embodies a new strat- gives us immediate insight into how to synthesize programs egy for synthesis—“infer types while synthesizing”—that for languages with rich type systems. Because rich types allows for efficient synthesis of polymorphic code while also constrain the set of possible programs dramatically, program giving us a framework to tackle GADTs and other advanced synthesis with types can lead to superior synthesis perfor- type features built on polymorphism. mance. On top of this, the type-directed synthesis style di- In section 2, we develop a constraint-based program syn- rectly supports type-directed programming, a hallmark of thesis based on a basic constraint-based type inference sys- richly-typed functional programming languages. tem. In section 3, we extend the system to account for GADTs. In section 4, we discuss our prototype implementation of 1.2 The Perils of Polymorphism this approach in our program synthesis tool, Scythe. And fi- However, in supporting rich types, in particular polymor- nally in section 5 and section 6 we close by discussing future phism, we run into a pair of problems that deserve special extensions and related work. attention. • The type enumeration problem: when dealing with 2 Constraint-based Program Synthesis polymorphic types, we must first instantiate them. We first develop a constraint-based program synthesis sys- However, there may be many possible instantiations tem for a core functional programming language featuring of a polymorphic type. For example, consider the map algebraic datatypes and polymorphism. To do this, we first function of type (a -> b) -> [a] -> [b].
Recommended publications
  • Lecture 4. Higher-Order Functions Functional Programming
    Lecture 4. Higher-order functions Functional Programming [Faculty of Science Information and Computing Sciences] 0 I function call and return as only control-flow primitive I no loops, break, continue, goto I (almost) unique types I no inheritance hell I high-level declarative data-structures I no explicit reference-based data structures Goal of typed purely functional programming Keep programs easy to reason about by I data-flow only through function arguments and return values I no hidden data-flow through mutable variables/state [Faculty of Science Information and Computing Sciences] 1 I (almost) unique types I no inheritance hell I high-level declarative data-structures I no explicit reference-based data structures Goal of typed purely functional programming Keep programs easy to reason about by I data-flow only through function arguments and return values I no hidden data-flow through mutable variables/state I function call and return as only control-flow primitive I no loops, break, continue, goto [Faculty of Science Information and Computing Sciences] 1 I high-level declarative data-structures I no explicit reference-based data structures Goal of typed purely functional programming Keep programs easy to reason about by I data-flow only through function arguments and return values I no hidden data-flow through mutable variables/state I function call and return as only control-flow primitive I no loops, break, continue, goto I (almost) unique types I no inheritance hell [Faculty of Science Information and Computing Sciences] 1 Goal
    [Show full text]
  • Higher-Order Functions 15-150: Principles of Functional Programming – Lecture 13
    Higher-order Functions 15-150: Principles of Functional Programming { Lecture 13 Giselle Reis By now you might feel like you have a pretty good idea of what is going on in functional program- ming, but in reality we have used only a fragment of the language. In this lecture we see what more we can do and what gives the name functional to this paradigm. Let's take a step back and look at ML's typing system: we have basic types (such as int, string, etc.), tuples of types (t*t' ) and functions of a type to a type (t ->t' ). In a grammar style (where α is a basic type): τ ::= α j τ ∗ τ j τ ! τ What types allowed by this grammar have we not used so far? Well, we could, for instance, have a function below a tuple. Or even a function within a function, couldn't we? The following are completely valid types: int*(int -> int) int ->(int -> int) (int -> int) -> int The first one is a pair in which the first element is an integer and the second one is a function from integers to integers. The second one is a function from integers to functions (which have type int -> int). The third type is a function from functions to integers. The two last types are examples of higher-order functions1, i.e., a function which: • receives a function as a parameter; or • returns a function. Functions can be used like any other value. They are first-class citizens. Maybe this seems strange at first, but I am sure you have used higher-order functions before without noticing it.
    [Show full text]
  • Program Synthesis with Grammars and Semantics in Genetic Programming
    Program Synthesis with Grammars and Semantics in Genetic Programming Stefan Forstenlechner UCD student number: 14204817 The thesis is submitted to University College Dublin in fulfilment of the requirements for the degree of DOCTOR OF PHILOSOPHY School of Business Head of School: Prof. Anthony Brabazon Research Supervisors: Prof. Michael O’Neill Dr. Miguel Nicolau External Examiner: Dr. John R. Woodward January 2019 Contents Contents i Abstract vii Statement of Original Authorship viii Acknowledgements ix List of Figures xi List of Tables xiv List of Algorithms xvii List of Abbreviations xviii Publications Arising xx I Introduction and Literature Review 1 1 Introduction 2 1.1 Aim of Thesis . .3 1.2 Research Questions . .4 1.3 Contributions . .7 1.3.1 Technical contributions . .8 1.4 Limitations . .8 1.5 Thesis Outline . .9 i CONTENTS 2 Related Work 12 2.1 Evolutionary Computation . 12 2.2 Genetic Programming . 14 2.2.1 Representation . 15 2.2.2 Initialization . 16 2.2.3 Fitness . 17 2.2.4 Selection . 17 2.2.5 Crossover . 19 2.2.6 Mutation . 19 2.2.7 GP Summary . 20 2.2.8 Grammars . 20 2.3 Program Synthesis . 23 2.3.1 Program Synthesis in Genetic Programming . 26 2.3.2 General Program Synthesis Benchmark Suite . 30 2.4 Semantics . 32 2.4.1 Semantic operators . 34 2.4.2 Geometric Semantic GP . 37 2.5 Conclusion . 38 II Experimental Research 40 3 General Grammar Design 41 3.1 Grammars for G3P . 41 3.2 Sorting Network . 42 3.3 Structure in Grammars . 43 3.4 Sorting Network Grammar Design .
    [Show full text]
  • Clojure, Given the Pun on Closure, Representing Anything Specific
    dynamic, functional programming for the JVM “It (the logo) was designed by my brother, Tom Hickey. “It I wanted to involve c (c#), l (lisp) and j (java). I don't think we ever really discussed the colors Once I came up with Clojure, given the pun on closure, representing anything specific. I always vaguely the available domains and vast emptiness of the thought of them as earth and sky.” - Rich Hickey googlespace, it was an easy decision..” - Rich Hickey Mark Volkmann [email protected] Functional Programming (FP) In the spirit of saying OO is is ... encapsulation, inheritance and polymorphism ... • Pure Functions • produce results that only depend on inputs, not any global state • do not have side effects such as Real applications need some changing global state, file I/O or database updates side effects, but they should be clearly identified and isolated. • First Class Functions • can be held in variables • can be passed to and returned from other functions • Higher Order Functions • functions that do one or both of these: • accept other functions as arguments and execute them zero or more times • return another function 2 ... FP is ... Closures • main use is to pass • special functions that retain access to variables a block of code that were in their scope when the closure was created to a function • Partial Application • ability to create new functions from existing ones that take fewer arguments • Currying • transforming a function of n arguments into a chain of n one argument functions • Continuations ability to save execution state and return to it later think browser • back button 3 ..
    [Show full text]
  • Topic 6: Partial Application, Function Composition and Type Classes
    Recommended Exercises and Readings Topic 6: Partial Application, • From Haskell: The craft of functional programming (3rd Ed.) Function Composition and Type • Exercises: • 11.11, 11.12 Classes • 12.30, 12.31, 12.32, 12.33, 12.34, 12.35 • 13.1, 13.2, 13.3, 13.4, 13.7, 13.8, 13.9, 13.11 • If you have time: 12.37, 12.38, 12.39, 12.40, 12.41, 12.42 • Readings: • Chapter 11.3, and 11.4 • Chapter 12.5 • Chapter 13.1, 13.2, 13.3 and 13.4 1 2 Functional Forms Curried and Uncurried Forms • The parameters to a function can be viewed in two different ways • Uncurried form • As a single combined unit • Parameters are bundled into a tuple and passed as a group • All values are passed as one tuple • Can be used in Haskell • How we typically think about parameter passing in Java, C++, Python, Pascal, C#, … • Typically only when there is a specific need to do • As a sequence of values that are passed one at a time • As each value is passed, a new function is formed that requires one fewer parameters • Curried form than its predecessor • Parameters are passed to a function sequentially • How parameters are passed in Haskell • Standard form in Haskell • But it’s not a detail that we need to concentrate on except when we want to make use of it • Functions can be transformed from one form to the other 3 4 Curried and Uncurried Forms Curried and Uncurried Forms • A function in curried form • Why use curried form? • Permits partial application multiply :: Int ‐> Int ‐> Int • Standard way to define functions in Haskell multiply x y = x * y • A function of n+1
    [Show full text]
  • Functional Programming Lecture 1: Introduction
    Functional Programming Lecture 13: FP in the Real World Viliam Lisý Artificial Intelligence Center Department of Computer Science FEE, Czech Technical University in Prague [email protected] 1 Mixed paradigm languages Functional programming is great easy parallelism and concurrency referential transparency, encapsulation compact declarative code Imperative programming is great more convenient I/O better performance in certain tasks There is no reason not to combine paradigms 2 3 Source: Wikipedia 4 Scala Quite popular with industry Multi-paradigm language • simple parallelism/concurrency • able to build enterprise solutions Runs on JVM 5 Scala vs. Haskell • Adam Szlachta's slides 6 Is Java 8 a Functional Language? Based on: https://jlordiales.me/2014/11/01/overview-java-8/ Functional language first class functions higher order functions pure functions (referential transparency) recursion closures currying and partial application 7 First class functions Previously, you could pass only classes in Java File[] directories = new File(".").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory(); } }); Java 8 has the concept of method reference File[] directories = new File(".").listFiles(File::isDirectory); 8 Lambdas Sometimes we want a single-purpose function File[] csvFiles = new File(".").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getAbsolutePath().endsWith("csv"); } }); Java 8 has lambda functions for that File[] csvFiles = new File(".")
    [Show full text]
  • Notes on Functional Programming with Haskell
    Notes on Functional Programming with Haskell H. Conrad Cunningham [email protected] Multiparadigm Software Architecture Group Department of Computer and Information Science University of Mississippi 201 Weir Hall University, Mississippi 38677 USA Fall Semester 2014 Copyright c 1994, 1995, 1997, 2003, 2007, 2010, 2014 by H. Conrad Cunningham Permission to copy and use this document for educational or research purposes of a non-commercial nature is hereby granted provided that this copyright notice is retained on all copies. All other rights are reserved by the author. H. Conrad Cunningham, D.Sc. Professor and Chair Department of Computer and Information Science University of Mississippi 201 Weir Hall University, Mississippi 38677 USA [email protected] PREFACE TO 1995 EDITION I wrote this set of lecture notes for use in the course Functional Programming (CSCI 555) that I teach in the Department of Computer and Information Science at the Uni- versity of Mississippi. The course is open to advanced undergraduates and beginning graduate students. The first version of these notes were written as a part of my preparation for the fall semester 1993 offering of the course. This version reflects some restructuring and revision done for the fall 1994 offering of the course|or after completion of the class. For these classes, I used the following resources: Textbook { Richard Bird and Philip Wadler. Introduction to Functional Program- ming, Prentice Hall International, 1988 [2]. These notes more or less cover the material from chapters 1 through 6 plus selected material from chapters 7 through 9. Software { Gofer interpreter version 2.30 (2.28 in 1993) written by Mark P.
    [Show full text]
  • Currying and Partial Application and Other Tasty Closure Recipes
    CS 251 Fall 20192019 Principles of of Programming Programming Languages Languages λ Ben Wood Currying and Partial Application and other tasty closure recipes https://cs.wellesley.edu/~cs251/f19/ Currying and Partial Application 1 More idioms for closures • Function composition • Currying and partial application • Callbacks (e.g., reactive programming, later) • Functions as data representation (later) Currying and Partial Application 2 Function composition fun compose (f,g) = fn x => f (g x) Closure “remembers” f and g : ('b -> 'c) * ('a -> 'b) -> ('a -> 'c) REPL prints something equivalent ML standard library provides infix operator o fun sqrt_of_abs i = Math.sqrt(Real.fromInt(abs i)) fun sqrt_of_abs i = (Math.sqrt o Real.fromInt o abs) i val sqrt_of_abs = Math.sqrt o Real.fromInt o abs Right to left. Currying and Partial Application 3 Pipelines (left-to-right composition) “Pipelines” of functions are common in functional programming. infix |> fun x |> f = f x fun sqrt_of_abs i = i |> abs |> Real.fromInt |> Math.sqrt (F#, Microsoft's ML flavor, defines this by default) Currying and Partial Application 4 Currying • Every ML function takes exactly one argument • Previously encoded n arguments via one n-tuple • Another way: Take one argument and return a function that takes another argument and… – Called “currying” after logician Haskell Curry Currying and Partial Application 6 Example val sorted3 = fn x => fn y => fn z => z >= y andalso y >= x val t1 = ((sorted3 7) 9) 11 • Calling (sorted3 7) returns a closure with: – Code fn y => fn z
    [Show full text]
  • Lecture Notes on First-Class Functions
    Lecture Notes on First-Class Functions 15-411: Compiler Design Rob Simmons and Jan Hoffmann Lecture 25 Nov 29, 2016 1 Introduction In this lecture, we discuss two generalizations of C0: function pointers and nested, anonymous functions (lambdas). As a language feature, nested functions are a nat- ural extension of function pointers. However, because of the necessity of closures in the implementation of nested functions, the necessary implementation strategies are somewhat different. 2 Function pointers The C1 language includes a concept of function pointers, which are obtained from a function with the address-of operator &f. The dynamic semantics can treat &f as a new type of constant, which represents the memory address where the function f is stored. S; η ` (∗e)(e1; e2) B K −! S; η ` e B ((∗_)(e1; e2) ;K) S; η ` &f B ((∗_)(e1; e2);K) −! S; η ` e1 B (f(_; e2) ;K) Again, we only show the special case of evaluation function calls with two and zero arguments. After the second instruction, we continue evaluating the argu- ments to the function left-to-right and then call the function as in our previous dynamics. We do not have to model function pointers using a heap as we did for arrays and pointers since we are not able to change the functions that is stored at a given address. It is relatively straightforward to extend a language with function pointers, be- cause they are addresses. We can obtain that address at runtime by referring to the label as a constant. Any label labl in an assembly file represents an address in memory (since the program must be loaded into memory in order to run), and can LECTURE NOTES NOV 29, 2016 First-Class Functions L25.2 be treated as a constant by writing $labl.
    [Show full text]
  • Learning Functional Programs with Function Invention and Reuse
    Learning functional programs with function invention and reuse Andrei Diaconu University of Oxford arXiv:2011.08881v1 [cs.PL] 17 Nov 2020 Honour School of Computer Science Trinity 2020 Abstract Inductive programming (IP) is a field whose main goal is synthe- sising programs that respect a set of examples, given some form of background knowledge. This paper is concerned with a subfield of IP, inductive functional programming (IFP). We explore the idea of generating modular functional programs, and how those allow for function reuse, with the aim to reduce the size of the programs. We introduce two algorithms that attempt to solve the problem and explore type based pruning techniques in the context of modular programs. By experimenting with the imple- mentation of one of those algorithms, we show reuse is important (if not crucial) for a variety of problems and distinguished two broad classes of programs that will generally benefit from func- tion reuse. Contents 1 Introduction5 1.1 Inductive programming . .5 1.2 Motivation . .6 1.3 Contributions . .7 1.4 Structure of the report . .8 2 Background and related work 10 2.1 Background on IP . 10 2.2 Related work . 12 2.2.1 Metagol . 12 2.2.2 Magic Haskeller . 13 2.2.3 λ2 ............................. 13 2.3 Invention and reuse . 13 3 Problem description 15 3.1 Abstract description of the problem . 15 3.2 Invention and reuse . 19 4 Algorithms for the program synthesis problem 21 4.1 Preliminaries . 22 4.1.1 Target language and the type system . 22 4.1.2 Combinatorial search .
    [Show full text]
  • Ml-Curry-4Up.Pdf
    CS 251 SpringFall 2019 2020 Principles of of Programming Programming Languages Languages Ben Wood More idioms for closures λ Ben Wood • Function composition Currying • Currying and partial application and Partial Application • Callbacks (e.g., reactive programming, later) and other tasty closure recipes • Functions as data representation (later) https://cs.wellesley.edu/~cs251/s20/ Currying and Partial Application 1 Currying and Partial Application 2 Function composition (right-to-left) Pipelines (left-to-right composition) fun compose (f,g) = fn x => f (g x) Common in functional programming. Closure “remembers” f and g : ('b -> 'c) * ('a -> 'b) -> ('a -> 'c) infix |> REPL prints something equivalent fun x |> f = f x fun sqrt_of_abs i = ML standard library provides infix operator o i |> abs |> Real.fromInt |> Math.sqrt fun sqrt_of_abs i = Math.sqrt(Real.fromInt(abs i)) fun sqrt_of_abs i = (Math.sqrt o Real.fromInt o abs) i val sqrt_of_abs = Math.sqrt o Real.fromInt o abs (F#, Microsoft's ML flavor, defines this by default) Right to left. Currying and Partial Application 3 Currying and Partial Application 4 Currying Example • Every ML function takes exactly one argument val sorted3 = fn x => fn y => fn z => z >= y andalso y >= x • Previously encoded n arguments via one n-tuple val t1 = ((sorted3 7) 9) 11 • Another way: 1. Calling (sorted3 7) returns closure #1 with: Take one argument and return a function that Code fn y => fn z => z >= y andalso y >= x takes another argument and… Environment: x ↦ 7 – Called “currying” after logician Haskell Curry 2. Calling closure #1 on 9 returns closure #2 with: Code fn z => z >= y andalso y >= x Environment: y ↦ 9, x ↦ 7 3.
    [Show full text]
  • CSE 341 : Programming Languages
    CSE 341 : Programming Languages Lecture 10 Closure Idioms Zach Tatlock Spring 2014 More idioms • We know the rule for lexical scope and function closures – Now what is it good for A partial but wide-ranging list: • Pass functions with private data to iterators: Done • Combine functions (e.g., composition) • Currying (multi-arg functions and partial application) • Callbacks (e.g., in reactive programming) • Implementing an ADT with a record of functions (optional) 2 Combine functions Canonical example is function composition: fun compose (f,g) = fn x => f (g x) • Creates a closure that “remembers” what f and g are bound to • Type ('b -> 'c) * ('a -> 'b) -> ('a -> 'c) but the REPL prints something equivalent • ML standard library provides this as infix operator o • Example (third version best): fun sqrt_of_abs i = Math.sqrt(Real.fromInt(abs i)) fun sqrt_of_abs i = (Math.sqrt o Real.fromInt o abs) i val sqrt_of_abs = Math.sqrt o Real.fromInt o abs 3 Left-to-right or right-to-left val sqrt_of_abs = Math.sqrt o Real.fromInt o abs As in math, function composition is “right to left” – “take absolute value, convert to real, and take square root” – “square root of the conversion to real of absolute value” “Pipelines” of functions are common in functional programming and many programmers prefer left-to-right – Can define our own infix operator – This one is very popular (and predefined) in F# infix |> fun x |> f = f x fun sqrt_of_abs i = i |> abs |> Real.fromInt |> Math.sqrt 4 Another example • “Backup function” fun backup1 (f,g) = fn x => case
    [Show full text]