MLStyle.jl
MLStyle.jl copied to clipboard
slurp Tuples
It's be good if MLStyle.jl could support slurping tuples like it does with arrays:
julia> using MLStyle
julia> @match [1, 2, 3, 4] [a, b..., c] => (a, b, c)
(1, [2, 3], 4)
julia> @match (1, 2, 3, 4) (a, b..., c) => (a, b, c)
ERROR: LoadError: PatternCompilationError(:(#= REPL[3]:1 =#), "unknown pattern syntax :(b...)")
Stacktrace:
[1] gen_match(::Expr, ::Expr, ::LineNumberNode, ::Module) at /home/mason/.julia/dev/MLStyle/src/MatchImpl.jl:474
[2] @match(::LineNumberNode, ::Module, ::Any, ::Any) at /home/mason/.julia/dev/MLStyle/src/MatchImpl.jl:454
in expression starting at REPL[3]:1
caused by [exception 1]
unknown pattern syntax :(b...)
It'd be nice if the final line evalutated to (1, (2, 3), 4).
Okay, considering this feature.
It's not difficult to add, but I hope to see some use cases.
Tuples are after all heterogeneous data, if you want to dynamically slice a tuple, I guess you might need to use a vector there?
Tuples are after all heterogeneous data, if you want to dynamically slice a tuple, I guess you might need to use a vector there?
It's not necessarily dynamic if you know the size of the Tuple, which is a part of it's type. If you write e.g.
f(t::Tuple) = @match t (a, b..., c) => (a, b, c)
the sizes can be statically known. I'm sure there's a tuple-recursion version, but here's a type inferrable @generated function that does it:
julia> @generated function f(t::NTuple{N}) where {N}
Expr(:tuple, :(t[1]), Expr(:tuple, (:(t[$i]) for i ∈ 2:N-1)...), :(t[$N]))
end
f (generic function with 2 methods)
julia> f((1,2,3,4,5,6,7,8))
(1, (2, 3, 4, 5, 6, 7), 8)
julia> @code_typed f((1,2,3,4,5,6,7,8))
CodeInfo(
1 ─ %1 = Base.getfield(t, 1, true)::Int64
│ %2 = Base.getfield(t, 2, true)::Int64
│ %3 = Base.getfield(t, 3, true)::Int64
│ %4 = Base.getfield(t, 4, true)::Int64
│ %5 = Base.getfield(t, 5, true)::Int64
│ %6 = Base.getfield(t, 6, true)::Int64
│ %7 = Base.getfield(t, 7, true)::Int64
│ %8 = Core.tuple(%2, %3, %4, %5, %6, %7)::NTuple{6,Int64}
│ %9 = Base.getfield(t, 8, true)::Int64
│ %10 = Core.tuple(%1, %8, %9)::Tuple{Int64,NTuple{6,Int64},Int64}
└── return %10
) => Tuple{Int64,NTuple{6,Int64},Int64}
This sort of thing is generally useful when you're dealing with functions that return tuples. Often in julia people write things like
julia> t = (1,2,3)
(1, 2, 3)
julia> a, b = t
(1, 2, 3)
julia> a
1
julia> b
2
but that's not very nice because in the destructuring step a, b = t, there's no way to know if you're throwing away data or not. Having an ability to slurp here would be quite nice because then you don't lose anything in the destructuring.
Yes, using generated functions can enable many advanced things without a performance cost, however it seems impossible for MLStyle to generate generated functions...
I hope the generated code from MLStyle macros will not depend on MLStyle itself, so if I generate generated functions, I have to generate it inside the macrocall wrapped block, which is always not at the top level and might be in the body of nested functions, where generated functions are invalid.
I feel it better to add another library to extend MLStyle with tuple slurping, where we can define some generated functions as runtime deps.
Yes, using generated functions can enable many advanced things without a performance cost, however it seems impossible for MLStyle to generate generated functions...
Ah I see. In that case, here's a non-generated function that can do it in a type stable manner for arbitrarily large Tuples
julia> function f(t::NTuple{N, Any}) where {N}
first(t), ntuple(i -> (j = i + 1; t[j]), Val(N-2)), last(t)
end
f (generic function with 1 method)
julia> f((1, 2.0, 3f0, "four", :five, Int32(6), Int16(7), Int8(8)))
(1, (2.0, 3.0f0, "four", :five, 6, 7), 8)
julia> @code_typed f((1, 2.0, 3f0, "four", :five, Int32(6), Int16(7), Int8(8)))
CodeInfo(
1 ─ %1 = Base.getfield(t, 1, true)::Int64
│ %2 = Base.getfield(t, 2, true)::Float64
│ %3 = Base.getfield(t, 3, true)::Float32
│ %4 = Base.getfield(t, 4, true)::String
│ %5 = Base.getfield(t, 5, true)::Symbol
│ %6 = Base.getfield(t, 6, true)::Int32
│ %7 = Base.getfield(t, 7, true)::Int16
│ %8 = Base.tuple(%2, %3, %4, %5, %6, %7)::Tuple{Float64,Float32,String,Symbol,Int32,Int16}
│ %9 = Base.getfield(t, 8, true)::Int8
│ %10 = Core.tuple(%1, %8, %9)::Tuple{Int64,Tuple{Float64,Float32,String,Symbol,Int32,Int16},Int8}
└── return %10
) => Tuple{Int64,Tuple{Float64,Float32,String,Symbol,Int32,Int16},Int8}
julia> Core.Compiler.return_type(f, Tuple{NTuple{100, Int}})
Tuple{Int64,NTuple{98,Int64},Int64}
julia> let t = ntuple(_ -> rand([1, "hi", 2.0, :bye, [true, false]]), 100)
Core.Compiler.return_type(f, Tuple{typeof(t)})
end
Tuple{Symbol,Tuple{Int64,String,Float64,String,Array{Bool,1},Int64,Float64,Symbol,Int64,Array{Bool,1},Float64,Symbol,String,Int64,Int64,Symbol,Float64,Symbol,String,Symbol,Array{Bool,1},Symbol,Symbol,Array{Bool,1},String,Array{Bool,1},Array{Bool,1},Float64,Int64,Array{Bool,1},Int64,Array{Bool,1},Float64,String,Float64,Int64,Symbol,Symbol,String,Symbol,Float64,Float64,Array{Bool,1},Symbol,Float64,Symbol,Array{Bool,1},Float64,String,Symbol,Float64,String,Symbol,Symbol,Array{Bool,1},Float64,String,Int64,Int64,Int64,Int64,Array{Bool,1},Float64,Array{Bool,1},String,Array{Bool,1},Float64,Float64,Int64,Float64,String,Symbol,Float64,Int64,String,Symbol,Int64,Symbol,String,Float64,Symbol,Float64,Int64,Int64,Symbol,Symbol,Array{Bool,1},String,Symbol,String,String,Symbol,Array{Bool,1},Array{Bool,1},Array{Bool,1},Array{Bool,1},Int64,Float64},Symbol}
This is really impressive. I think I can do this in MLStyle. I'll implement it before the next patch release.
Some hints if you want to implement it before I make actions. Feel free to ask me anything on slack if you have questions/problems..
https://github.com/thautwarm/MLStyle.jl/blob/8bba7028879a2f37774af1d919859dfb4bda83a5/src/MatchImpl.jl#L196
tuple pattern: https://github.com/thautwarm/MLStyle.jl/blob/8bba7028879a2f37774af1d919859dfb4bda83a5/src/AbstractPatterns/impl/BasicPatterns.jl#L102
vector slurping pattern: https://github.com/thautwarm/MLStyle.jl/blob/8bba7028879a2f37774af1d919859dfb4bda83a5/src/AbstractPatterns/impl/BasicPatterns.jl#L150
Fantastic, I'll look into it if I have time in the near future!