Make my own alias infix operator

Hi there :slight_smile:

I see that custom infix operators are a long-standing object to discussion, and I like to ask if there is a way today, how I can alias to >>

As an explanation, my VSCode wont tab complete the \circ and I am already used to >> from F#
But I am unsure if I can make such an alias at all currently, and if so, if its possible to define it as infix operator.

Thanks a lot for your help

The biggest reason you couldn’t is both are already used for important functions, function composition and bitshifting respectively, so you’d break something if you could reassign one name to another, which you can’t for constants.

Let’s instead assume you’re talking about an operator that nobody uses; you can make a separate variant of any accepted infix operator by suffixing it like >>̂ (type >>\hat<tab> in REPL), but you cannot define arbitrary text as infix operators. The reason you shouldn’t do that is infix operators have fixed precedence according to their parser, and suffixed variants share precedence with their original. has different precedence from >>, so you don’t want >>̂ to do what does.

Example of operator aliasing:

julia> const >>̂ = ∘
∘ (generic function with 3 methods)

julia> >>̂ # verify successful aliasing
∘ (generic function with 3 methods)

julia> (==(6)∘length)("apple1")
true

julia> (==(6)>>̂length)("apple1")
true

But the precedence remains different, so assuming the alias is fully equivalent is incorrect and will cause bugs in operator chains.

julia> Base.operator_precedence.((:∘, ://, :>>̂)) # larger is prioritized
(12, 13, 14)

julia> Meta.@lower f1 >>̂ f2 ∘ f3
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = f1 >>̂ f2
│   %2 = %1 ∘ f3
└──      return %2
))))

julia> Meta.@lower f1 ∘ f2 >>̂ f3
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = f2 >>̂ f3
│   %2 = f1 ∘ %1
└──  

julia> Meta.@lower f1 >>̂ f2 // denom
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = f1 >>̂ f2
│   %2 = %1 // denom
└──      return %2
))))

julia> Meta.@lower f1 ∘ f2 // denom
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = f2 // denom
│   %2 = f1 ∘ %1
└──      return %2
))))

I suggest making a separate thread for this, maybe someone else has ran into and figured out this problem more recently.

1 Like

Thanks a lot for taking the time to answer this :slight_smile:

This reads like its mostly a chore, and really not a straight forward process.
So I am mostly limited to use symbols, that are either not used at all, or those who have the same precedence level of the original symbol.

I am really not interested in the bitshift operator, but I guess you would still discourage me to replace it. It seems like jumping through hoops, just to get normal composition/piping running.

Will copy/paste for now ^^

P.S: I opened an issue: \circ does not expand · Issue #3403 · julia-vscode/julia-vscode · GitHub

Does the Fast Unicode Math Characters VSCode extension help?

1 Like

Yep, that works!

Thanks

Hunh? You can do const ∘ = >>. You just can’t do so after touching Base’s export. This is how shadowing all built in names work.

But your point stands about the mismatched precedences being troublesome.

3 Likes

You’re right, by “reassign” I was assuming that the exported names >>, ∘ were already used. But I suppose it’s very possible to not need both function composition and bitshifting, so shadowing one of the exported names is more likely. Was trying to think of a way to distinguish if a name was created in a module or exported from another one, but I can’t recall one.maybe asymbol in names(amodule; all=true).

2 Likes

From the old Julia parser, one can look up new, unused operators recgonized by Julia with a given level of precendence. In your case, here are all the operators with the same precedence as \circ:

(define prec-times       (add-dots '(* / ⌿ ÷ % & · · ⋅ ∘ × |\\| ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗ ⨟)))

You can pick any one of these and use as a drop-in replacement for \circ, e.g., \odot:

julia> const ⊙ = ∘

Admmittedly, the list is a bit short on ASCII symbols, so if tab replacement of symbols is generally non-functional in your VSCode setup, then you’re out of luck.

2 Likes

Just a remark: >> in F# is forward, i.e., left-to-right, composition, whereas adheres to the mathematical convention of right-to-left. Guess that making it an alias would be very confusing at best.
(It’s fine to define it accordingly though, e.g., >>(funs...) = ∘(reverse(funs)...), but maybe using another symbol due to shadowing Base.:>> and different precedence as mentioned by others already).

To me, bit shifting sounds like something imperative, and that I am not quite so fond of.

@HanD Oh, thanks for making the effort to present that so orderly to me.
I generally like to avoid tab completions.

Not because of myself, first and foremost, but because I like to have a symbol that I can show to colleagues, without getting looked at from the corner of the eye, with a raised eyebrow, or two. :smiley:

@bertschi Thanks - yeah, and F# is using << instead of .
They generally recommend |> and >>, since this is more how you read it.

I ultimately found (more than) what I wanted in this suggestion, and really hope it gets accepted. :smiley:

What does that even mean? How can 1 pure binary operation distinguish an imperative or functional paradigm? Looking it up, F# has bitshifts >>> and <<<.

F# is a multi paradigm language, and I consider everything imperative, that essentially mutates data. And it is, as far as I can tell, mainly used with low level languages?

Bitshifts work on immutable integers, so this doesn’t seem logical

4 Likes

>>> and the other bitshift operators are pure functions, so they should be compatible with functional programming, as far as I can tell.

They are low-level, but is that a problem?

1 Like

I assumed, that it is an impure function.

I don’t comprehend, how a shift of a bit is not mutating state?
Is it, because the underlying data structure is immutable?

It would be not functional on an array, then.

Do you consider multiplying an integer by two to be an impure and mutating operation? Bitshifts are not fundamentally different.

3 Likes

I know very little on the topic of low level operations.
I am a newbie to programming in general.

And no, I don’t consider multiplying integer mutating state.
I simply thought, shifting bits meant to actually change them in memory.
It seems like this is not the case.

It calculates an outout value that is equal to a bit shifted version of the input value. It is similar to multiplying a value by another value.

That would be the same as any other function, if you use it to calculate a new value and then overwrite the old value in an array.

1 Like

So the bit is not really “shifted”, but it’s just a calculation, that does, as if?

Try it out and see how the bits shift.

julia> 1465 << 1
2930

julia> 1465 << 2
5860

julia> 1465 << 3
11720

julia> string(1465, base = 2, pad = 16)
"0000010110111001"

julia> string(1465 << 1, base = 2, pad = 16)
"0000101101110010"

julia> string(1465 << 2, base = 2, pad = 16)
"0001011011100100"

julia> string(1465 << 3, base = 2, pad = 16)
"0010110111001000"
1 Like