Closed haxscramper closed 3 years ago
Ambiguous call errors are very easy to resolve and in the absence of name only arguments I think it's a decent substitute. Forward declarations should match their signature exactly anyway. The compiler already complains when you don't export the forward declaration but do export the main declaration.
I've experimentally done some converting of procs default arguments to constants and removing this feature would break that kind of code.
#b and c are meant to called as named only arguments in this context
proc hi(a : int, b : int = 5, c : int = 6) =
when b is static:
discard
else:
discard
#macro to create this
proc hi(a : int, b : int, c : int) =
discard
proc hi(a : int, b : int) =
const c = 6
proc hi(a : int, c : int) =
const b = 5
proc hi(a : int) =
const
b = 5
c = 6
Regardless ambiguous call errors are so easy to fix that removing this feature doesn't seem worth it.
Is useful for DSL ?, kinda like:
proc render(img: string) = imgImpl(img)
proc render(video: string) = videoImpl(video)
render img="kitten.png" # <img src="kitten.png"></img>
render video="bongocat.webm" # <video src="bongocat.webm"></video>
Too simple example (not really a DSL) but you get the idea anyways... 🤔
@solo989 you seem to be overly focused on my latter note related to the ambiguous call errors - it is not the main point I'm trying to make, I just provided an example where this feature is somewhat undesired. My main argument is - if it is almost never used, why keep this feature anyway?
Also, I'm having a hard time figuring out a real-world use case for your code example - can you please elaborate, what kind of problem does it solve?
@juancarlospaco
Is useful for DSL ?, kinda like:
Sure, according to Hyrum's Law there might actually be someone who even built DSL that functions this way, but I would choose renderImg
or renderVideo
every single time. I mean, if you already have to write render img
already, why does it have to be a separate obscure feature? It is also not clear from the procedure name alone. In addition, render img = "kitten.png"
does not compile, as render img = 12
is parsed as
Asgn
Command
Ident "render"
Ident "img"
IntLit 12
So you actually have to do this, which is a clear step back from renderVideo
:
proc render(img: string): string = "<img src=\"" & img & "\"></img>"
proc render(video: string): string = "<video src=\"" & video & "\"></video>"
# /home/jail/prog.nim(5, 8) Error: undeclared identifier: 'img'
echo render(img="kitten.png" )
echo render(video="bongocat.webm" )
My point is if the problems with it are trivial to deal with and the maintenance cost is nil then there is no point in removing this feature.
I spend very little time dealing with ambiguous errors. Most of my time is spent debugging complete type mismatches or trying to decipher error messages generated by templates or macros calling each other.
Also with juans example you could generate code similar to this.
template r(p,img,video) : untyped =
echo p(img=img)
echo p(video=video)
echo p(somethingElse1="someValue1")
echo p(somethingElse2="someValue2")
echo p(somethingElse3="someValue3")
#add a dozen other procs with the same name here
r(render,img,video)
as Opposed to
template r(p1,p2,p3,p4,p5,etc,img,video) : untyped =
echo p1(img=img)
echo p2(video=video)
echo p3(somethingElse1="someValue1")
echo p4(somethingElse2="someValue2")
echo p5(somethingElse3="someValue3")
#add a dozen other procs here
r(render1,render2,render3,render4,render5,img,video)
Separating it into multiple procs with seperate names would make it harder to deal with.
Sure, according to Hyrum's Law there might actually be someone who even built DSL that functions this way
Ok, I upvoted it, kill it with fire. 🙂:+1:
@solo989 again - this is not a question whether the feature can be used somehow. But I feel there should be a justification in the form of some real-world use (not some abstract never-before-seen issue), where problem cannot be solved (easily) using different functionality, otherwise it makes sense to trim down on unused features, especially when they are obscure, never used and can be easily replicated using already existing features:
# Actually clear what procedure does based on it's name
proc renderImg(img: string): string = "<img src=\"" & img & "\"></img>"
proc renderVideo(video: string): string = "<video src=\"" & video & "\"></video>"
# render ?? image or video, and you need to remember to call correct `type = `
# also does not work as good with autocomplete.
proc render(img: string): string = "<img src=\"" & img & "\"></img>"
proc render(video: string): string = "<video src=\"" & video & "\"></video>"
template r1(p, inImg, inVideo) : untyped =
echo p(img = inImg)
echo p(video = inVideo)
template r2(p, inImg, inVideo) : untyped =
# Joining identifier is a more general feature
echo `p Img`(inImg)
echo `p Video`(inVideo)
# Works the same way
r1(render,"img","video")
r2(render,"img","video")
Ok heres an example. I ended up making by own version of walk with a bunch of possibly static arguments. The way I made it was rather clunky and full of whens and some macro nonsense. If I were to recreate it I would probably do something like this
my code actually had a bunch more optional args then this so I was always passing them as named arguments anyway also command syntax doesn't look that good when the argument lists get long
template ifWhen(a : bool, b : untyped) : untyped =
if a:
b
template ifWhen(a : static[bool], b : untyped) : untyped =
when a:
b
iterator walk(files : string, slowCheck = staticCheck, slowCheck2 = staticCheck) : string {.splitInto4Functions.} =
for file in files:
ifWhen slowCheck:
ifWhen slowCheck2:
discard
else:
discard
else:
discard
body
iterator walk(files : string, slowCheck = staticCheck, slowCheck2 = staticCheck) : string =
ifWhen slowCheck:
fastCheck = true
ifWhen slowCheck2:
fastCheck2 = true
else:
fastCheck2 = false
else:
fastCheck = false
for file in files:
if fastCheck:
discard
etc
body
for file in walk(dir):
discard
for file in walk(dir,slowCheck=slowCheckProc):
discard
now you could move some of those checks outside of the loops but then you have to reconfigure your code
you could even drop the ifwhens as the ifs will probably be turned into if true or if false anyway
I have no idea if nim optimizes efficiently for static default arguments when they are passed in normally.
Also turning generics into non generics makes symbol binding more predictable.
Frankly removing features that have no maintenance cost and are potentially useful for creating more efficient generic code seems wasteful.
Also a lot of those obscure never before seen issues are only obscure until an algorithm is implemented that makes it obvious that this is how you should have solved the problem in the first place.
I think we are still in early days of figuring out how we can help the computer generate much more efficient code with less and less intervention from the programmer.
Well, it seems like there is a conflict of two points of view - "0.04% of use cases is not enough to justify feature existence" vs "if it does not trouble anyone, why remove it?". I think we need someone who can say whether it is really zero-maintenance or not. If it is, then I guess the issue could be closed and this feature could continue with it's "out of sight, out of mind" status.
On a side note - I still don't understand your example, or how named parameter overloading helps it. You want to have a parameter (check? I would assume it is a callback in the form of proc(item: file): bool
) and you want to save on execution time by splitting procedure declaration into 2^(number-of-args)
overloads that user would have to select based on the argument names, where implementation would check whether an argument was actually static, and act at compile-time based on that?
iterator walk(files: string, slowCheck = staticCheck, slowCheck2 = staticCheck): string =
for file in files:
ifWhen slowCheck: # Avoid calling preciate if it is a static value to save on execution time?
ifWhen slowCheck2:
discard
else:
discard
else:
discard
# ... two more overloads with `slowCheck` and `slowCheck2` parameters
iterator walk(files: string): string =
const
slowCheck = staticCheck
slowCheck2 = staticCheck
for file in files:
ifWhen slowCheck:
ifWhen slowCheck2:
discard
else:
discard
else:
discard
I have no idea if nim optimizes efficiently for static default arguments when they are passed in normally.
import std/macros
proc arg(a: static[int] = 12) =
expandMacros:
echo a
arg(12)
echo ["12"]
12
If you mean static[T]
arguments - they behave the same way as const
, so constant folding and other optimizations apply.
I should've said default arguments that are normally non static that have static values as their defaults. I'm not sure nim optimizes for such a case since it would require creating two different versions of the proc.
Actually most defaults on most functions are static.
I was thinking each function would have two versions of a parameter. A const value default or a runtime value.
You could even add another variation and add a third static value that the user can pass in.
The feature is a natural outcome of the used algorithm and hypothetical alternative implementations would have the same behavior or at least can easily ensure to support the feature with minimal cost. Disallowing this form of overloading is more costly as it requries special logic in a compiler.
Alright, if keeping it is cheaper than removing lets leave it alone. Not really useful or used, but it does not really matter.
Sorry to comment on a closed issue like this but IMO the best style to write this:
proc render(img: string) = imgImpl(img)
proc render(video: string) = videoImpl(video)
render img="kitten.png" # <img src="kitten.png"></img>
render video="bongocat.webm" # <video src="bongocat.webm"></video>
Would be this:
type Image = distinct string
type Video = distinct string
proc render(img: Image) = imgImpl(img)
proc render(video: Video) = videoImpl(video)
render Image("image.png")
render Video("video.webm")
An experimental feature, which allow declaring procedures with identical signatures, but different argument names:
This feature is used very infrequently - I was able to find only 131 procedure declarations (60 groups) from
300842
processed procedures (script) - approximately0.04%
of declarations use this feature. Full list of findings included in details. I considered only type names, which means thatproc[T: SomeInteger](arg: T)
andproc[T: string | char](arg: T)
are placed in the same group, so the actual number is even lower. Processing was done per-file.If anything this is an anti-feature, which allows for very unexpected behavior and cryptic errors in forward declarations -
forward(a: int)
cannot be used to declaredforward(b: int)
. So I suggest it to be removed/deprecated entirely as it does not provide any substantial value.