`(x, y)` works but `x => y` doesn't

This really seems like a bug to me, but perhaps I’m missing some kind of obscure difference between (x, y) and x => y syntax…

import Base: ==, big
import IntervalArithmetic: Interval

struct Foo <: Real
  v
end

# Necessary for printing
==(x::Foo, y::Float64) = x.v == y
big(x::Foo) = big(x.v)

# This works...
@show ("abc", Interval(Foo(0.0), Foo(1.0)))
# But this doesn't work...
@show "abc" => Interval(Foo(0.0), Foo(1.0))

What’s going on here? Why can I no haz arrow pairs?

Btw, the error message is

ERROR: LoadError: MethodError: no method matching atomic(::Type{Interval{Foo}}, ::Interval{Foo})
Closest candidates are:
  atomic(::Type{Interval{T}}, ::Interval) where T<:AbstractFloat at /Users/skainswo/.julia/packages/IntervalArithmetic/UR6Qe/src/intervals/conversion.jl:122
  atomic(::Type{Interval{T}}, ::S) where {T<:AbstractFloat, S<:Real} at /Users/skainswo/.julia/packages/IntervalArithmetic/UR6Qe/src/intervals/conversion.jl:95
Stacktrace:
 [1] convert(#unused#::Type{Interval{Foo}}, x::Interval{Foo})
   @ IntervalArithmetic ~/.julia/packages/IntervalArithmetic/UR6Qe/src/intervals/conversion.jl:20
 [2] Pair
   @ ./pair.jl:12 [inlined]
 [3] Pair(a::String, b::Interval{Foo})
   @ Base ./pair.jl:15
 [4] top-level scope
   @ show.jl:955
in expression starting at /Users/skainswo/dev/research/julia/boxes/src/bug.jl:16

which doesn’t make any sense considering that convert(::Type{Interval{Foo}}, x::Interval{Foo}) is implemented here: IntervalArithmetic.jl/conversion.jl at master · JuliaIntervals/IntervalArithmetic.jl · GitHub.

They do different things, so I’m not sure why you expect to behave the same.

The difference is that a tuple just takes the two values as they are, in the stacktrace of the Pair constructor you can see convert(::Type{Interval{Foo}}, x::Interval{Foo}) is called, and that function fails because it internally tries to call an undefined method

2 Likes

At the risk of getting sidetracked, why does Julia have redundant Pair and tuple constructs?

But why doesn’t it just call theconvert implementation here: IntervalArithmetic.jl/conversion.jl at master · JuliaIntervals/IntervalArithmetic.jl · GitHub?

Yeah, that does seem odd. I don’t quite understand what’s happening. You might have to file an issue at the IntervalArithmetic.jl repo.

See `Pair{A,B}` vs. `Tuple{A,B}` - #5 by Tamas_Papp and links therein.

1 Like

Redundant? They have different semantics: tuple is a container of arbitrary size, Pair is only for, well, pairs of the type key => value.

That bit confuses me as well at the moment but I agree with Cameron that sounds like a but in IntervalArithmetic.jl

4 Likes

I’ve opened What is required to use custom types in `Interval`s? · Issue #476 · JuliaIntervals/IntervalArithmetic.jl · GitHub

1 Like

I’m not aware of any other languages that make this distinction. In most languages I’ve experienced tuple is explicitly not a container of arbitrary size. That’s what distinguishes it from arrays etc. Instead it’s used as a value type. (Fwiw I’m coming from a background of Python, Haskell, Ocaml, F#, C++, Scala, etc but perhaps there are others I’m missing)

Ha, I think he meant that you can make a tuple of any size, not that you can change the size of an existing tuple. :slight_smile:

Ah I see… But then I’m not sure why the distinction with Pair

To summarize the main differences between Tuple and Pair, which I’m mostly stealing from the link provided by @Jeff_Emanuel:

  • You can make a tuple with any number of elements, but a pair can only have two elements.
  • We have special syntax for Pair: =>. That syntax doesn’t quite make sense for tuples, since tuples can have more than two elements.
  • Tuples are covariant, whereas pairs are invariant:
julia> (1, 1) isa Tuple{Real, Real}
true

julia> (1 => 1) isa Pair{Real, Real}
false

That being said, I often use two element tuples instead of pairs. Pairs do typically imply a key => value directionality.

4 Likes

Then I guess you have heard of std::tuple and std::pair in C++?

2 Likes

True, forgot about that… I would argue that the distinction doesn’t make any sense in c++ either though

Practically speaking, Pair allows you to have the syntax => and dispatch on it without clashing with generic tuples, which are a much lower level concept. Is that enough of a distinction? As I’m trying to say, they have different semantics.

1 Like

Sure, but scala also offers the x -> y syntax and just desugars it into a pair (x, y). The one thing that makes some sense to me is that tuples are covariant but Pairs are invariant. But even then I don’t understand why make Pairs invariant? Are they mutable?

The argument that this is obviously necessary in the first place seems to be invalidated by the fact that there exist so many extremely successful and well-considered language designs that don’t have separate pair and tuple types. Why add complexity?

1 Like

You may have missed

But I don’t have much to add to what has been already said in the discussion on the same topic that has been linked above.

3 Likes

All paratetric types in julia are invariant other than Tuple. This is largely for historical reasons as far as I understand. Tuple is a very special type that you couldn’t implement in the language itself.

:man_shrugging:

Most languages don’t have multiple dispatch, so the name of a type matters less in those languages. This is only extra complexity if you come to julia with baggage from other languages. They’re just different types.

It’s like asking why Complex{Int}(1, 2) is a different type from Rational{Int}(1, 2). Sure, they’re both parametric structs with identical layouts, but they have completely different, unrelated semantic meanings.

10 Likes