Part of my learning process with Julia has been to abuse the type system in various fun ways to see what’s possible. My latest monstrosity uses type parameters for partial application of functions:

```
module TypeAbuse
struct Fix{Fn,Args,KW}
Fix{Fn}() where {Fn} = new{Fn,(),(;)}()
Fix{Fn,Args}() where {Fn,Args} = new{Fn,Args,(;)}()
Fix{Fn,Args,KW}() where {Fn,Args,KW} = new{Fn,Args,KW}()
end
function (::Fix{Fn,Args,KW})(moreargs...) where {Fn,Args,KW}
Fn(_cat(Args, moreargs)...; KW...)
end
_cat(a, b) = (a..., b...)
end
let c = TypeAbuse.Fix{+,1}()
@show c
@show c(2) == 3
end
let c = TypeAbuse.Fix{digits,(),(; base = 2)}()
println()
@show c
@show c(5) == [1, 0, 1]
end
```

```
c = Main.TypeAbuse.Fix{+, 1, NamedTuple()}()
c(2) == 3 = true
c = Main.TypeAbuse.Fix{digits, (), (base = 2,)}()
c(5) == [1, 0, 1] = true
```

I benchmarked calling `TypeAbuse.Fix{log,5}()`

with `rand()`

against the equivalent `Base.Fix1`

call, and on my system calling both `log(5, rand())`

and the `TypeAbuse.Fix`

takes ~24ns, where `Base.Fix1`

takes ~33ns, so it’s basically zero overhead at least in that particular case. I assume this is due to it being a singleton type and having both the function and its partially applied arguments available statically, meaning no construction of a new `Fix`

instance and no field lookups?

Now, this benchmark result got me curious about how inadvisable this is, exactly. I know it will create a new type for every new `Fn`

, `Args`

and `KW`

, increase compilation time at least to some extent, and only work with `Args`

s / `KW`

s that are `isbitstypes`

(I think?), but considering those caveats is this actually as much of a crime against the type system as it originally seemed to me? As a beginner it feels like it could also actually be a useful optimization to shave off some nanoseconds from calling partially applied functions, assuming a case where those nanoseconds *actually* matter and the number of different type parameters doesn’t get out of hand.