lambda

所属分类:collect
开发工具:R
文件大小:0KB
下载次数:0
上传日期:2019-09-15 08:09:33
上 传 者sh-1993
说明:  R中的函数编程,
(Functional programming in R,)

文件列表:
.Rbuildignore (95, 2019-09-15)
.travis.yml (66, 2019-09-15)
DESCRIPTION (485, 2019-09-15)
NAMESPACE (458, 2019-09-15)
R/ (0, 2019-09-15)
R/duck.R (843, 2019-09-15)
R/framework.R (33991, 2019-09-15)
R/introspection.R (2430, 2019-09-15)
R/objects.R (41, 2019-09-15)
man/ (0, 2019-09-15)
man/UseFunction.Rd (1418, 2019-09-15)
man/duck.Rd (1093, 2019-09-15)
man/framework.Rd (4135, 2019-09-15)
man/introspection.Rd (2230, 2019-09-15)
man/lambda.r-package.Rd (13595, 2019-09-15)
tests/ (0, 2019-09-15)
tests/test-all.R (38, 2019-09-15)
tests/testit/ (0, 2019-09-15)
tests/testit/test-auto_replace.1.R (749, 2019-09-15)
tests/testit/test-auto_replace.2.R (1201, 2019-09-15)
tests/testit/test-auto_replace.3.R (269, 2019-09-15)
tests/testit/test-dispatching.1.R (1752, 2019-09-15)
tests/testit/test-ellipsis_arguments.1.R (864, 2019-09-15)
tests/testit/test-ellipsis_arguments.2.R (569, 2019-09-15)
tests/testit/test-examples.R (295, 2019-09-15)
tests/testit/test-factorial.1.R (229, 2019-09-15)
tests/testit/test-factorial.2.R (495, 2019-09-15)
tests/testit/test-fill_args.R (814, 2019-09-15)
tests/testit/test-function_args.1.R (534, 2019-09-15)
tests/testit/test-function_type.1.R (271, 2019-09-15)
tests/testit/test-heaviside_step.1.R (183, 2019-09-15)
tests/testit/test-heaviside_step.2.R (259, 2019-09-15)
tests/testit/test-infix.1.R (257, 2019-09-15)
tests/testit/test-optional_arguments.1.R (1857, 2019-09-15)
tests/testit/test-optional_arguments.2.R (753, 2019-09-15)
tests/testit/test-parse_transforms.1.R (461, 2019-09-15)
tests/testit/test-parse_transforms.2.R (392, 2019-09-15)
tests/testit/test-parse_transforms.3.R (427, 2019-09-15)
... ...

[![Build Status](https://travis-ci.org/zatonovo/lambda.r.png)](https://travis-ci.org/zatonovo/lambda.r) Description =========== Provides a syntax for writing functional programs in R. Lambda.r has a clean syntax for defining multi-part functions with optional guard statements. Simple pattern matching is also supported. Types can be easily defined and instantiated using the same functional notation. Type checking is integrated and optional, giving the programmer complete flexibility over their application or package. Basic Usage =========== Defining a function ------------------- Functions are defined using `%as%` notation. Any block of code can be in the function definition. ```R fib(n) %as% { fib(n-1) + fib(n-2) } ``` Pattern matching ---------------- Multi-part function definitions are easily constructed. For simple criteria, pattern matching of literals can be used directly in lambda.r. ```R fib(0) %as% 1 fib(1) %as% 1 ``` Strings can also be pattern matched within definitions. Guard statements ---------------- Executing different function variants within a multi-part function sometimes requires more detail than simple pattern matching. For these scenarios a guard statement is used to define the condition for execution. Guards are simply an additional clause in the function definition. ```R fib(n) %when% { n >= 0 } %as% { fib(n-1) + fib(n-2) } ``` A function variant only executes if the guard statements all evaluate to true. As many guard statements as desired can be added in the block. Just separate them with either a new line or a semi-colon. ```R fib(n) %when% { is.numeric(n) n >= 0 } %as% { fib(n-1) + fib(n-2) } ``` Note that in the above example the type check can be handled using a type declaration, which is discussed below. For functions defined in multiple parts, each separate function variant is evaluated in the same order as they are defined. Hence a less restrictive variant that evaluates to true defined early in the chain of function definitions will take precedence over variants defined later. (If you are following along in sequence, the pattern matches for `fib(0)` and `fib(1)` will never be called since the first definition `fib(n)` will always evaulate to true). Types ===== Lambda.R introduces types as an alternative to classes. Types are data structures with type information attached to it. Like classes, constructors exist for types and one type can inherit from another type. The difference is that types do not have embedded methods. In functional programming, functions are first class so there is no need to embed them within the data structure. Using types provides type safety, which means that a function variant will only execute if the types are correct. Defining a type --------------- Types are defined by defining their constructor. We define constructors using an uppercase function name. The return value of the constructor is automatically typed. Hence the value x will be of type Integer. ```R Integer(x) %as% x ``` Instantiating the type is as simple as calling the function. Check the type using the standard S3 introspection function `class`. The `%isa%` operator can also be used to test whether an object is a particular type. ```R x <- Integer(5) x %isa% Integer ``` Type declarations ----------------- Type constraints can be added to a function. These constraints specify the type of each input argument in addition to the return type. Using this approach ensures that the arguments can only have compatible types when the function is called. The final type in the constraint is the return type, which is checked after a function is called. If the result does not have the correct return type, then the call will fail. ```R fib(n) %::% Integer : Integer fib(0) %as% Integer(1) fib(1) %as% Integer(1) fib(n) %as% { fib(n-1) + fib(n-2) } fib(x) ``` The call `fib(1)` will fail because `1` is not of type `Integer`. ```R > fib(1) Error in UseFunction("fib", ...) : No valid function for 'fib(1)' ``` Properly typing the argument by calling `fib(Integer(1))` will give the correct output. Note that pattern matching works even with the custom type. ```R > fib(Integer(1)) [1] 1 attr(,"class") [1] "Integer" "numeric" ``` Type constraints must be declared prior to the function implementation. Once declared, the type declaration will retain scope until another type declaration with the same number of parameters is declared (see tests/types.R for an example). Legacy types ------------ There are plenty of built-in types that are supported just like custom types defined in lambda.r. Use the same syntax for these types. In the example above we can just as easily declare ```R fib(n) %::% numeric : numeric ``` or even ```R fib(n) %::% Integer : numeric ``` NOTE: For objects of type function, due to the precedence rules of the parser, you cannot specify 'function' in a type constraint. Instead use 'Function'. ```R # Do this make.gen(n) %::% numeric : Function # Don't do this make.gen(n) %::% numeric : function ``` Type Variables -------------- Type constraints are useful, but too specific of a constraint can destroy the polymorphism of a function. To preserve this while still retaining some type safety you can use a type variable. With type variables the actual type is not checked. Instead it is the relationship between types that are checked. ```R fib(n) %::% a : a fib(0) %as% 1 fib(1) %as% 1 fib(n) %as% { fib(n-1) + fib(n-2) } ``` In this type constraint, both the input and output types must match. Note that the only characters valid for a type variable are the lowercase letters (i.e. a-z). If you need more than this for a single function definition, you've got other problems. The Ellipsis Type ----------------- The ellipsis can be inserted in a type constraint. This has interesting properties as the ellipsis represents a set of arguments. To specify that input values should be captured by the ellipsis, use ```...``` within the type constraint. For example, suppose you want a function that multiplies the sum of a set of numbers. The ellipsis type tells lambda.r to bind the types associated with the ellipsis type. ```R sumprod(x, ..., na.rm=TRUE) %::% numeric : ... : logical : numeric sumprod(x, ..., na.rm=TRUE) %as% { x * sum(..., na.rm=na.rm) } > sumprod(4, 1,2,3,4) [1] 40 ``` Alternatively, suppose you want all the values bound to the ellipsis to be of a certain type. Then you can append ```...``` to a concrete type. ```R sumprod(x, ..., na.rm=TRUE) %::% numeric : numeric... : logical : numeric sumprod(x, ..., na.rm=TRUE) %as% { x * sum(..., na.rm=na.rm) } > sumprod(4, 1,2,3,4) [1] 40 > sumprod(4, 1,2,3,4,'a') Error in UseFunction(sumprod, "sumprod", ...) : No valid function for 'sumprod(4,1,2,3,4,a)' ``` If you want to preserve polymorphism but still constrain values bound to the ellipsis to a single type, you can use a type variable. Note that the same rules for type variables apply. Hence a type variable represents a type that is not specified elsewhere. ```R sumprod(x, ..., na.rm=TRUE) %::% a : a... : logical : a sumprod(x, ..., na.rm=TRUE) %as% { x * sum(..., na.rm=na.rm) } > sumprod(4, 1,2,3,4) [1] 40 > sumprod(4, 1,2,3,4,'a') Error in UseFunction(sumprod, "sumprod", ...) : No valid function for 'sumprod(4,1,2,3,4,a)' ``` The Don't-Care Type ------------------- Sometimes it is useful to ignore a specific type in a constraint. Since we are not inferring all types in a program, this is an acceptable action. Using the ```.``` within a type constraint tells lambda.r to not check the type for the given argument. For example in ```R f(x, y) %::% . : numeric : numeric```, the type of ```x``` will not be checked. One Shot ======== Here is the complete example using built-in types: ```R fib(n) %::% numeric : numeric fib(0) %as% 1 fib(1) %as% 1 fib(n) %as% { fib(n-1) + fib(n-2) } fib(5) seal(fib) ``` To ignore types altogether, just omit the type declaration in the above listing and the code will evaluate the same. Here is the same example with custom types: ```R Integer(x) %as% x fib(n) %::% Integer : Integer fib(0) %as% Integer(1) fib(1) %as% Integer(1) fib(n) %as% { fib(n-1) + fib(n-2) } x <- Integer(5) fib(x) ``` The `seal` command in the first example prevents new statements from being added to an existing function definition. Instead new definitions reset the function. Typically you don't need this function as lambda.r will auto-replace function definitions that have the same signature. Sugar Coating ============= All the great features of R function calls are still supported in lambda.r. In addition, lambda.r provides some parse transforms to add some extra features to make application development even faster. Object Attributes ----------------- Attributes are a form of meta data that decorate an object. This information can be used to simplify type structures retaining polymorphism and compatibility with existing functions while providing the detail needed for your application. Lambda.R provides convenient syntax for interacting with attributes via the `@` symbol. ```R Temperature(x, system, units) %as% { x@system <- system x@units <- units x } ``` These attributes can then be accessed in guards and function bodies using the same syntax. ```R freezing(x) %::% Temperature : logical freezing(x) %when% { x@system == 'metric' x@units == 'celsius' } %as% { if (x < 0) { TRUE } else { FALSE } } ``` Note that outside of lambda.r you must use the standard attr() function to access specific attributes. Also note that attributes have not been tested with S4 objects. Optional Arguments ------------------ A nice convenience in R is the ability to specify optional arguments with default values. Lambda.R preserves this feature in multipart function definitions. Functions are matched based on the order in which they are defined, and this holds true with functions with optional arguments. ```R Temperature(x, system="metric", units='celsius') %as% { x@system <- system x@units <- units x } > ctemp <- Temperature(20) > ctemp [1] 20 attr(,"system") [1] "metric" attr(,"units") [1] "celsius" attr(,"class") [1] "Temperature" "numeric" ``` The Ellipsis Argument --------------------- Support for the ellipsis argument is built into lambda.r. Required arguments must still be matched, while any additional arguments will be enclosed in the ellipsis. Here's an example using the plant data included in R's lm help page. ```R regress(formula, ..., na.action='na.fail') %as% { lm(formula, ..., na.action=na.action) } ctl <- c(4.17,5.58,5.18,6.11,4.50,4.61,5.17,4.53,5.33,5.14) trt <- c(4.81,4.17,4.41,3.59,5.87,3.83,6.03,4.89,4.32,4.69) data <- data.frame(group=gl(2,10,20,labels=c("Ctl","Trt")), weight=c(ctl, trt)) lm.D9 <- regress(weight ~ group, data=data) ``` Care does need to be used with the ellipsis as it behaves like a greedy match, so subsequent definitions may not work as you intend when using the ellipsis argument in a function variant. Named Arguments --------------- The examples above all hint at supporting named arguments. Named arguments can be mixed and matched with positional arguments just as in legacy function definitions. ```R lm.D9 <- regress(data=data, weight ~ group) ``` Auto Replace and Sealing Definitions ------------------- As of version 1.1.0, lambda.r can detect a duplicate function signature and update an existing definition. This means development is more efficient since you can re-source files and the existing definitions will update as you expect. This process is compatible with multi-part function definitions and type constraints. Do note that when using type constraints, only functions associated with the active type constraint can be auto replaced. The reason is that there can be two identical function signatures and lambda.r really has no way of knowing which one you mean. Hence, you have to tell lambda.r via the type constraint. For example take this simple reciprocal function. There are two type constraint clauses, and three total function variants. The signatures for variants 2 and 3 are identical, so the only thing that distinguishes them are the different type constraints associated with each one. Notice that there is an explicit bug in the definition of variant 2. ```R reciprocal(n) %::% numeric : numeric reciprocal(0) %as% stop("Reciprocal of 0 is undefined") reciprocal(n) %as% { 2/n } reciprocal(n) %::% character : numeric reciprocal(n) %as% { reciprocal(as.numeric(n)) } ``` To change the definition of variant 2, you must re-declare the first type constraint. Otherwise, lambda.r would not know whether to update variant 2 or 3. ```R reciprocal(n) %::% numeric : numeric reciprocal(n) %as% { 1/n } ``` Lambda.R has no way of knowing whether a function definition is complete or not. Explicitly telling lambda.r will ensure that any new function definitions will reset the function as opposed to append another definition. ```R seal(freezing) ``` If providing a broad interface, be careful not to seal the function. Sealing is analogous to making a variable final in Java, such that no further modifications can be made. The key difference is that attempting to add further defintions to the sealed function will overwrite the existing definition. This behavior is intended to make application and package development more iterative. Introspection ============= A function in lambda.r has a lot of meta data attached to it. Accessing the raw data can be overwhelming, so lambda.r provides facilities to extract the useful bits. Viewing basic information about a function is accomplished by just typing out the function in the shell. This results in a dump of the type declarations and function signatures for the function. ```R > fib [[1]] fib(n) %::% Integer:Integer fib(0) %as% ... [[2]] fib(n) %::% Integer:Integer fib(1) %as% ... [[3]] fib(n) %::% Integer:Integer fib(n) %as% ... ``` Actual function bodies are not displayed to minimize clutter. Examining Functions ------------------- To view a full function definition, use the 'describe' function to get the definition of a specific function variant. The numbers separating each variant is the index to use. ```R > describe(fib,3) function(n) { Integer ( fib ( n - 1 ) + fib ( n - 2 ) ) } ``` Examining Types --------------- A type constructor is similar to a normal function, and the same technique works to view a type body. ```R > describe(Integer,1) function(x) { x } ``` Monads ------ If you want to play around with monads in R, `lambda.r` has your back. Here are some examples. Note that `lambda.tools` implements some common monadic operators. ``` Maybe(a) %:=% a Just(a) %:=% Maybe(a) Nothing() %:=% Maybe(NA) mreturn(x) %as% Just(x) m %>>=% g %when% { is.null(m) } %as% NULL m %>>=% g %::% Just : Function : Maybe m %>>=% g %as% g(m) m %>>=% g %::% Nothing : Function : Maybe m %>>=% g %as% m m %>>=% g %::% Just : Function : Maybe m %>>=% g %as% g(m) ``` Composition ``` f %.% g %:=% function(...) f(g(...)) > unsafelogsqrt <- log %.% sqrt > unsafelogsqrt(100) [1] 2.302585 ``` Monadic composition ``` f %>=>% g %:=% { function(x) f(x) %>>=% g } f %<=<% g %:=% { function(x) g(x) %>>=% f } safelog(x) %::% numeric : Maybe safelog(x) %when% { x <= 0 } %as% Nothing() safelog(x) %:=% Just(log(x)) safesqrt(x) %::% numeric : Maybe safesqrt(x) %when% { x <= 0 } %as% Nothing() safesqrt(x) %:=% Just(sqrt(x)) safelogsqrt <- safelog %<=<% safesqrt ``` Debugging --------- The standard debug function will not work with lambda.r functions. Instead, use the included functions debug.lr and undebug.lr. These functions will allow you to debug through a complete multipart function call. Known Limitations ================= If you try to break lambda.r, you will most likely succeed. There are things that won't work, but most use cases should work fine. Do let me know if you find something that fails, but don't break it just to break it. Below are some things that won't work. 1. Complex mix and match of named and positional arguments ```R lm.D9 <- regress(data=data, formula=weight ~ group, NULL) ``` Don't do this, please. It's bad style. What's New ========== Version 1.1.5 ------------- + Support the ellipsis (...) in type constraints + Support a typed ellipsis (numeric...) in type constraints + Support ellipsis type variable (a...) in type constraints + Support debug.lr in functions defined in packages (currently locked) + Show which functions are being debugged + Handle default arguments that execute a function + Support more pattern matching (like empty lists) + Support guards of the form f(a,b,c) %when% { sum(a,b,c) == 1 } %as% { 1 } + Support guards of the form f(x) %when% { length(grep('foo',x)) > 0 } %as% { 1 } + Support proper import namespace for packages + Install from github using devtools: install_github('lambda.r','zatonovo') + Travis CI integration: https://travis-ci.org/zatonovo/lambda.r Version 1.1.1 ------------- + Support Function in every type position (only supported for return type) + Auto-replacing a function with 0 arguments fails + Fix type inheritance + Functions that return non-scalar values work as default values on arguments + Support pattern matching on NULL and NA + Support pattern matching on special symbol EMPTY Version 1.1.0 ------------- + Handle function types in type declarations + Support type variables + Auto-replace function definitions with a matching signature (no need for seal) + Handle 0 argument functions Future ====== + Support *apply and lambda expressions in guard statements + Support defining operators + Lock functions by default (check when next function is different name). Use public() as way to indicate function can be globally modified + Think about supporting namespaces + Support take, drop, cycle + Support partial function application + Check for side effects + Support tail recursion + Support type inference

近期下载者

相关文件


收藏者