How come these work
==(2).(1:5)
≠(2).(1:5)
>(2).(1:5)
and these don’t ?
+(2).(1:5)
*(2).(1:5)
^(2).(1:5)
How come these work
==(2).(1:5)
≠(2).(1:5)
>(2).(1:5)
and these don’t ?
+(2).(1:5)
*(2).(1:5)
^(2).(1:5)
The syntax func.(collection)
means applying func
to the collection
. For example
julia> (x -> 2x).(1:5)
5-element Vector{Int64}:
2
4
6
8
10
In your example, ==(2)
, ≠(2)
and >(2)
are functions whereas +(2)
, *(2)
and ^(2)
are not.
julia> ==(2) isa Function
true
julia> +(2) isa Function
false
For more information, ==(2)
is called a partial function application. This is a term from functional programming which means to fix an argument to a function. Specifically, ==(2)
is equivalent to defining ==(x) = y -> x == y
which is equivalent to Base.Fix2(==, 2)
. See the documentation of Base.Fix2
for more information about that function.
For readers who wonder why partial function applications are useful, they are mostly useful in combination with higher order functions, that is, functions which take functions such as map
or filter
. For example, compare
julia> map(x -> x < 2, 1:5)
5-element Vector{Bool}:
1
0
0
0
0
julia> filter(x -> x == 2, 1:5)
1-element Vector{Int64}:
2
julia> filter(s -> contains(s, "ar"), ["foo", "bar"])
1-element Vector{String}:
"bar"
to
julia> map(<(2), 1:5)
5-element Vector{Bool}:
1
0
0
0
0
julia> filter(==(2), 1:5)
1-element Vector{Int64}:
2
julia> filter(contains("ar"), ["foo", "bar"])
1-element Vector{String}:
"bar"
Can this notation be used to abbreviate this?
filter( x-> any( contains.(x,["a","b"]) ), ["aa","bb","cc","dd"] )
Not that I know of. Sounds like a nice idea though to allow applying AND and OR to functions. What does work here is to move the OR (any
) inside the contains
:
julia> filter(contains(r"a|b"), ["aa", "bb", "cc", "dd"])
2-element Vector{String}:
"aa"
"bb"
Could this functional form be available for more functions ?
2 examples
It would be nice to write this
@pipe "A=1,B=2,C=3" |> split(_,',') |> split.(_,'=')
As
"A=1,B=2,C=3" |> split(',') .|> split('=')
And it would be nice to write this
conv(x) =
try Date( x, dateformat"dduuuyyyy") catch
try Currency_Dict[x] catch
try Buy_Sell_Dict[x] catch
try parse(Float64,x) catch
try Symbol(x) catch
end end end end
end
as
conv(x) = x |>
try Date(dateformat"dduuuyyyy") catch
try Currency_Dict catch
try Buy_Sell_Dict catch
try parse(Float64) catch
try Symbol catch
end end end end
end
The challenging part about this is that this is the following currently return something when the arguments are strings.
julia> split(",")
1-element Vector{SubString{String}}:
","
julia> split("=")
1-element Vector{SubString{String}}:
"="
Nonetheless, you correctly identified that split(::Char)
is not currently defined. Thus we could do:
julia> Base.split(c::AbstractChar) = Base.Fix2(Base.split, c)
julia> split(',')
(::Base.Fix2{typeof(split), Char}) (generic function with 1 method)
julia> "A=1,B=2,C=3" |> split(',') .|> split('=')
3-element Vector{Vector{SubString{String}}}:
["A", "1"]
["B", "2"]
["C", "3"]
help?> Base.Fix2
Fix2(f, x)
A type representing a partially-applied version of the two-argument function f, with the second argument fixed to
the value "x". In other words, Fix2(f, x) behaves similarly to y->f(y, x).
A better definition would also allow for the other keyword arguments:
Base.split(c::AbstractChar; kwargs...) = Base.Fix2(Base.split, dlm; kwargs...)
The above could be potentially confusing, so I would prefer the following:
julia> Base.split(; dlm=isspace) = Base.Fix2(Base.split, dlm)
julia> "A=1,B=2,C=3" |> split(dlm=',') .|> split(dlm='=')
3-element Vector{Vector{SubString{String}}}:
["A", "1"]
["B", "2"]
["C", "3"]
The above does not allow for the other keywords. A keyword compatible version would look like this.
julia> Base.split(; dlm=isspace, kwargs...) = x->Base.split(x, dlm; kwargs...)
julia> "a b c" |> split()
3-element Vector{SubString{String}}:
"a"
"b"
"c"
julia> "a b c" |> split(limit=2)
2-element Vector{SubString{String}}:
"a"
"b c"
Hot patching Base
like this is frowned upon. It might be fine in a terminal project, but can create issues for composability. Another approach is to create your own split
in a module or package and then import that into your namespace.
julia> module SplitFix2
split(c::AbstractChar) = Base.Fix2(Base.split, c)
split(; dlm=isspace, kwargs...) = x->Base.split(x, dlm; kwargs...)
end
Main.SplitFix2
julia> import .SplitFix2: split
julia> "a b c" |> split(limit=2)
2-element Vector{SubString{String}}:
"a"
"b c"
julia> "A=1,B=2,C=3" |> split(dlm=',')
3-element Vector{SubString{String}}:
"A=1"
"B=2"
"C=3"
julia> Base.split == SplitFix2.split
false
julia> @which split
Main.SplitFix2
Thanks Mark.
Most functions have one argument that is the data input, and so the one I’d like to be passable from a pipe or chain. Maybe I’ll create my own versions with slightly different names (to avoid the issue you mention with split( ::string ) already being defined ).
splt(dlm) = x -> (split(x,dlm))
fltr(f) = x -> filter( f, x )
Dat(format) = x -> Date( x, format)
pars(typ) = x -> parse(typ,x)
CSV_write(file) = x -> CSV.write( file, x )
I like your version with split(dlm=',')
as you’re able to keep the same name.