zatonovo / lambda.r Goto Github PK
View Code? Open in Web Editor NEWFunctional programming in R
Functional programming in R
This occurs when,
Base case with no warning,
load_data(user_id) %::% character : z
load_data(user_id) %as% { data.frame(x='a', y=1) }
load_data('u')
x y
1 a 1
Warning case where load_data returns a data.frame with,
class(Transaction(data.frame(x='a', y=1), 'raw'))
[1] "Transaction" "data.frame"load_data(user_id) %::% character : z
load_data(user_id) %as% { Transaction(data.frame(x='a', y=1), 'raw') }
load_data('u')
x y
1 a 1
Warning message:
In if (class(result) %in% sapply(raw.args, class)) { :
the condition has length > 1 and only the first element will be used
> a(f, ...) %as% { list(...) }
> a(1, 2,3,4)
[[1]]
[1] 2
[[2]]
[1] 3
[[3]]
[1] 4
[[4]]
[1] NA
[[5]]
[1] NA
[[6]]
[1] NA
[[7]]
[1] NA
[[8]]
[1] NA
> a(1, 2)
[[1]]
[1] 2
[[2]]
[1] NA
[[3]]
[1] NA
[[4]]
[1] NA
> a(1)
list()
> a(1, b=3)
$b
[1] 3
Support the following forms:
f(...) %::% ... : z
f(...) %::% numeric... : z
f(...) %::% a... : z
Not sure if this is possible, but it would be nice to preserve lazy evaluation under certain circumstances. Calling list(...)
evaluates all arguments passed to the function. Possibly a clever use of substitute
could work, otherwise it will likely require low-level C changes.
Plan(user.id) %as% { list(user.id=user.id) }
Plan(account.id) %as% { list(account.id=account.id) }
Plan(account.id=4)
Error in function (user.id) : unused argument(s) (account.id = 4)
In R-devel, there are now optional checks asserting that x && y
is not called with length(x) > 1
or length(y) > 1
. These checks can be enabled with _R_CHECK_LENGTH_1_LOGIC2_=true
- I'll assume they'll eventually will be on by default. I guess that CRAN will enable them before that. I know that the CRAN incomin checks already have it enabled.
lambda.r produces:
Error in !is.na(line <- it()) && line$token != "SPECIAL" :
'length(x) = 9 > 1' in coercion to 'logical(1)'
already when installed.
$ Rscript --version
R scripting front-end version 3.6.0 Under development (unstable) (2019-03-09 r76216)
$ _R_CHECK_LENGTH_1_LOGIC2_=true R --vanilla --quiet
> install.packages("lambda.r")
Installing package into ‘/home/hb/R/x86_64-pc-linux-gnu-library/3.6’
(as ‘lib’ is unspecified)
--- Please select a CRAN mirror for use in this session ---
trying URL 'https://cloud.r-project.org/src/contrib/lambda.r_1.2.3.tar.gz'
Content type 'application/x-gzip' length 25559 bytes (24 KB)
==================================================
downloaded 24 KB
* installing *source* package ‘lambda.r’ ...
** package ‘lambda.r’ successfully unpacked and MD5 sums checked
** R
** byte-compile and prepare package for lazy loading
Error in !is.na(line <- it()) && line$token != "SPECIAL" :
'length(x) = 9 > 1' in coercion to 'logical(1)'
Error: unable to load R code in package ‘lambda.r’
Execution halted
ERROR: lazy loading failed for package ‘lambda.r’
* removing ‘/home/hb/R/x86_64-pc-linux-gnu-library/3.6/lambda.r’
* restoring previous ‘/home/hb/R/x86_64-pc-linux-gnu-library/3.6/lambda.r’
The downloaded source packages are in
‘/tmp/RtmpFG73rb/downloaded_packages’
Warning message:
In install.packages("lambda.r") :
installation of package ‘lambda.r’ had non-zero exit status
> sessionInfo()
R Under development (unstable) (2019-03-09 r76216)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS
Matrix products: default
BLAS: /home/hb/software/R-devel/trunk/lib/R/lib/libRblas.so
LAPACK: /home/hb/software/R-devel/trunk/lib/R/lib/libRlapack.so
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
loaded via a namespace (and not attached):
[1] compiler_3.6.0 tools_3.6.0 tcltk_3.6.0
>
How does lambda.r handle inheritance at the moment?
I think a haskell style typeclass implementation would be awesome.
Here is an example:
require(lambda.r)
Function(x) %when% { is.function(x) } %as% x
addn.factory(n) %::% numeric : Function
addn.factory(n) %as% {
addn(x) %as% {
x + n
}
Function(addn)
}
Error in get_name(it) :
Function must start with a symbol (instead of '(')
For example, this would be legit, but nonsensical:
PointCartesian(x, y) %as% list(x = x, y = y)
PointCartesian("a", "b")
How do you make sure that both x and y are numerics?
No way to supply NULL as a default value with typed functions
Portf_S(name) %::% character: list
Portf_S(name) %as% {Portf_S(name, NULL)}
Portf_S(name, endDate) %::% character: Date: list
Portf_S(name, endDate) %as% {
obj = mg(name)
if(!is.null(endDate)) obj@endDate = endDate
list(
name = name,
invID = obj@invID,
obj = obj,
invType = getInvType(name),
cacheKey = GenerateKey(name))}
Example as below, lambda.r function is not working the same as R functions:
for function call f1(Sys.Date(), 1,2) ,
Sys.Date() should be matched to start by default. But it is not.
library(lambda.r)
f1 (start = Sys.Date(), ...) %as% {
vs <- list(...)
print(vs)
NULL
}
f1(Sys.Date(), 1,2)
[[1]]
[1] "2016-07-27"
[[2]]
[1] 1
[[3]]
[1] 2
NULL
f1(start = Sys.Date(), 1,2)
[[1]]
[1] 1
[[2]]
[1] 2
NULLf2 <- function(start = Sys.Date(), ...) {
vs <- list(...)
print(vs)
NULL
}
f2(Sys.Date()-1, 1, 2)
[[1]]
[1] 1
[[2]]
[1] 2
NULL
f2(start = Sys.Date()-1, 1, 2)
[[1]]
[1] 1
[[2]]
[1] 2
NULL
Using arithmetic operators in function form (e.g. *
) causes an error when used in conjunction with dplyr pipes and lambda.r. See example below:
require(dplyr)
require(lambda.r)
## works
foo1(bar) %as% {
bar * 2
}
## works
foo2(bar) %as% {
`*`(bar, 2)
}
## works
foo3 <- function(bar) {
bar %>% `*`(2)
}
## error - "Error in parse(text = text) ... unexpected '*'"
foo4(bar) %as% {
bar %>% `*`(2)
}
EDIT: this seems to be specific to multiplication; addition +
works
typeclass and deriving are very useful for composing interfaces.
and it should be easy mapping typeclass to S3 class in R.
I encountered a bug with this behavior when using a type variable for the return type for a lambda.r function definition:
Point(x,y) %as% list(x=x,y=y)
distance(a,b) %::% Point : Point : z
distance(a,b) %as% { ((a$x - b$x)^2 + (a$y - b$y)^2)^.5 }
After defining the above type and function, executing this in R throws an error:
> a <- Point(1, 2)
> b <- Point(3, 4)
> distance(a, b)
Error in arg.types[[x]] : subscript out of bounds
Using an explicit type constraint works properly for the return type,
Point(x,y) %as% list(x=x,y=y)
distance(a,b) %::% Point : Point : numeric
distance(a,b) %as% { ((a$x - b$x)^2 + (a$y - b$y)^2)^.5 }
Executing in R,
> a <- Point(1, 2)
> b <- Point(3, 4)
> distance(a, b)
[1] 2.828427
Just two thoughts on the README.md.
> Int(x) %as% x
> int(x) %as% x
> Int(1)
[1] 1
attr(,"class")
[1] "Int" "numeric"
> int(1)
[1] 1
For example take this simple reciprocal function. It has two three clauses and two type constraints. There is an explicit bug in the body of the second variant. Note that the signatures for variants 2 and 3 are identical and the only thing that distinguishes them are ther type constraints.
Note "two three clauses". Also it would be useful to define the term "clause". I believe you are referring to a block of function variants associated with a single type constraint, but that is only my best guess based on context. Also I would suggest specifically saying "the body of the second variant of the first clause". Lastly, ther -> their.
Sorry to be a stickler - I comment because I care! Great work on the package.
The reason Is that an integer does not inherit from numeric in S3.
> class(as.integer(4))
[1] "integer"
> is.integer(as.integer(4))
[1] TRUE
> is.numeric(as.integer(4))
[1] TRUE
Instead what is needed is to convert a numeric
reference to is.numeric()
, the same way that Function
maps to is.function()
.
If we define the following:
TypeA(x) %as% x
TypeB(y) %as% y
multiplyA(a, b) %::% TypeA:TypeA:TypeB
multiplyA(a, b) %as% {
TypeB(a * b)
}
Then we can create the following value:
r$> myB <- multiplyA(TypeA(2), TypeA(3))
r$> myB
[1] 6
attr(,"class")
[1] "TypeB" "TypeA" "numeric"
The following should throw an error, because myB
should not be a TypeA
.
r$> multiplyA(myB, myB)
[1] 36
attr(,"class")
[1] "TypeB" "TypeA" "numeric"
TestRefClass <- setRefClass("TestRefClass",
fields = list(
test_date = 'Date',
test_num = 'numeric'
))
TestLambdaFun(test_obj_) %::% TestRefClass : TestRefClass
TestLambdaFun(test_obj_) %as% {
return(test_obj_)
}
testObj <- TestRefClass$new(
test_date = as.Date('2015-01-01'),
test_num = 1)
Let's try to pass testObj
to TestLambdaFun
> TestLambdaFun(testObj)
<S4 Type Object>
attr(,".xData")
<environment: 0x7ff36ae0b638>
attr(,"class")
[1] "TestLambdaFun" "TestRefClass"
Warning message:
In class(result) <- c(type, class(result)) :
Setting class(x) to multiple strings ("TestLambdaFun", "TestRefClass", ...); result will no longer be an S4 object
Here is an example:
ColorRGBhex(hexcode) %::% character : .
ColorRGBhex(hexcode) %when% {
length(hexcode) == 1
} %as% {
ColorRGBhex_(tolower(hexcode))
}
ColorRGBhex_(hexcode) %::% character : .
ColorRGBhex_(hexcode) %when% {
all(strsplit(hexcode, "")[[1]] >= "0")
all(strsplit(hexcode, "")[[1]] <= "f")
} %as% {
Color_(hexcode)
}
It would be nice if I can write it like this:
ColorRGBhex(hexcode) %::% character : .
ColorRGBhex(hexcode) %let% {
hexcode = tolower(hexcode)
hex_chars = strsplit(hexcode, "")[[1]]
} %when% {
length(hexcode) == 1
all(hex_chars >= "0")
all(hex_chars <= "f")
} %as% {
ColorRGBhex_(hexcode)
}
This saves both typing and computation time.
It would be awesome to be able to do something like this:
require(lambda.r)
Function(x) %when% { is.function(x) } %as% x
addn.factory(n) %::% numeric : Function(numeric : numeric)
addn.factory(n) %as% {
addn = function(x) {
x + n
}
Function(addn)
}
Only allow consecutive function clauses by default. Provide an option to allow a function to be 'generic' and be added to iteratively.
I've just installed lambda.r, and when trying to load the package it fails with an error: "Error: package 'parser' could not be loaded.
A search on CRAN says that 'parser' has been removed.
For example:
OddNumber(x) %::% integer : .
OddNumber(x) %when% {
(x %% 2) == 1
} %as% {
x
}
Feed it an even number and it will complain:
> OddNumber(2L)
Error in UseFunction(type.fn, type.name, ...) :
No valid function for 'OddNumber(2)'
But it doesn't say what exactly went wrong. How about something like this:
OddNumber(x) %::% integer : .
OddNumber(x) %when% {
if(x %% 2 == 1) TRUE else {
warning("OddNumber: must feed me an ODD number!")
FALSE
}
} %as% {
x
}
But this currently won't work:
Error in parse_guard(it) : Invalid symbol '{'in function definition
The body_fn needs to construct a signature that includes the default values. Also fill_args needs to ignore default values.
(a, b) <- list(rnorm(100), d="foo", e="bar") results in a = rnorm(100), b = list(d='foo', e='bar')
During the discussion about vectorizing the lambda.tools function onlyif(condition, fn, x) when length(condition) == length(x), we decided that vectorization can be implemented via the vectorization of pattern matching in lambda.r.
Please delete! I misunderstood some things :-) Thanks for the excellent library!
Hi. Not sure if this is a bug or a misinterpretation, on my part, of how the package is meant to be used...
How would I get the return value of 'to_sample' (below) to be of type Sample/Whole/numeric -- which is what I expected/hoped for -- rather than Sample/Milliseconds/Whole/numeric (see testthat results pasted below)?
Thanks.
Code
Whole(n) %::% numeric : numeric
Whole(n) %when% {
n >= 0
} %:=% {
floor(n)
}
Natural(n) %::% numeric : numeric
Natural(n) %when% {
is.count(n)
} %:=% {
n
}
Sample(n) %::% numeric : numeric
Sample(n) %:=%
Whole(n)
Milliseconds(n) %::% numeric : numeric
Milliseconds(n) %:=%
Whole(n)
SamplePerSecond(n) %::% numeric : numeric
SamplesPerSecond(n) %:=%
Natural(n)
to_sample(ms, sample_per_s) %::% Milliseconds : SamplesPerSecond : Sample
to_sample(ms, sample_per_s) %as% {
per_ms <-
0.001
floor(ms * sample_per_s * per_ms) %>% Sample()
}
Failing test result
> library("testthat")
>
> context("to_sample")
>
> thousand_per_second <-
+ SamplesPerSecond(1000)
>
>
> test_that("zero samples can be found in zero milliseconds", {
+ sample_duration <-
+ Milliseconds(0)
+
+ to_sample(sample_duration, thousand_per_second) %>%
+ expect_equal(Sample(0))
+ })
Error: Test failed: 'zero samples can be found in zero milliseconds'
.
not equal to Sample(0).It needs to loop through the function defs and undebug them.
I am a user of StatET (I assume a lot of other users) and unfortunately there is no way for StatET to index a defition of a function defined by lambda.r;
Are there any plans to make lambda.r compatible with R syntax? Are there any IDE which support lambda.r syntax?
EvalCall(quote_call) %::% call : numeric
EvalCall(quote_call) %as% {
eval(quote_call)
}
EvalCall_Native = function(quote_call){ eval(quote_call) }
> EvalCall(quote({2+2}))
Error in UseFunction(type.fn, type.name, ...) :
No valid function for 'EvalCall({)'
For native:
> EvalCall_Native(quote({2+2}))
[1] 4
Noticed a problem with %as%
in combination with %>%
:
R version 4.1.0 (2021-05-18)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.2 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8
[4] LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] lambda.r_1.2.4
loaded via a namespace (and not attached):
[1] compiler_4.1.0 formatR_1.10 tools_4.1.0
testf() %as% {unique(2)}
testf()
[1] 2
testf() %as% {2 %>% unique()}
testf()
Error in 2 %>
%:
could not find function "%>
%"
Please note the problematic line break.
Ideally this would be vectorized:
abs(x) %when% { x < 0 } %as% -x
abs(x) %as% x
Undebug all functions currently being debugged.
I was inspired by this library and by my experience in functional programming in general to try to hack up a shorter lambda syntax for R. %as% didn't work in blocks (with lapply etc). Here's what I came up with, inspired by Haskell and Clojure. It's pretty ugly, but it is a nice proof of concept. Wonder if it would be possible to use defmacro or strmacro to supply the function as a {} block instead of a string... Something for a future lambda.r, if we could make it cleaner/saner?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.