12

I am writing a simple function return similar words.

Clear[similarWords]
similarWords[string_]:=Nearest[WordList[],string]

I want to add another argument n which is optional. When it presents, it controls the number of words returns.

Clear[similarWords]
similarWords[string_,n_:???]:=Nearest[WordList[],string,n]

But the problem is, what should I put it ???. I cannot figure it out.

The only way I can come up is

Clear[similarWords]
similarWords[string_,n_:-1]:=If[n==-1,Nearest[WordList[],string],Nearest[WordList[],string,n]]

Is there a neater way?

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
matheorem
  • 17,132
  • 8
  • 45
  • 115

4 Answers4

12

See @AlbertRetey's answer for all but trivial cases.


Don't use Optional, nor If. Use two definitions.

similarWords[string_] := Nearest[WordList[], string]

similarWords[string_, n_] := Nearest[WordList[], string, n]

I prefer this over just using arg___ and passing all arguments into Nearest because it keeps the responsibility for argument checking with similarWords. But of course just passing down everything is easier and quicker to write, and it's what I'd do in an interactive session (as opposed to a package or a situation where reusability and reliability is more important).

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
11

for your simple example Szabolcs suggestions is certainly the best you can do. If for some reason in a less simple situation you want the behavior you described with just one definition this is what you could do:

similarWords[string_, n_: Automatic] := If[n === Automatic,
  Nearest[WordList[], string],
  Nearest[WordList[], string, n]
]

note that Automatic is just a symbol whose name is guaranteed to have no definition and seems to fit the intented behavior, it has no special functionality builtin. Technically you could just as well use any other "tag", including -1 as you suggested...

EDIT there has been some discussion if and when this or the two definition approach are to be prefered, and I think it is pretty clear that the two definition approach is best when it doesn't lead to code duplication. If it does, you might be better off with the single definition approach in this answer. Alternatively you could extract one or more functions which do what is common to both definitions and only call those and have the code which is different in the bodies of the two definitions...

Albert Retey
  • 23,585
  • 60
  • 104
  • +1. I also do exactly this in such cases, and prefer this over having 2 definitions. I also believe that this is the recommended solution, used most frequently in internal code / development. – Leonid Shifrin Sep 09 '16 at 13:25
  • @LeonidShifrin Ah, so my original solution is already as good as recommended :D, good to hear this. – matheorem Sep 09 '16 at 13:31
  • +1. One situation where this may make this more difficult is if you decide to extend the argument pattern in the future, e.g. add options too. – Szabolcs Sep 09 '16 at 13:34
  • @matheorem Feel free to change the accept. – Szabolcs Sep 09 '16 at 13:43
  • @Szabolcs I frequently use functions with both. What I do is of course to restrict the type of the optional arguments, like e.g. f[arg:Except[_?OptionQ]:Automatic, opts:OptionsPattern[]] (or stricter types when possible), and it works just fine. The reason I dislike two definition - based solution is code duplication and the need to maintain that (keep them in sync as the code changes). Over the time, more often than not such places become the origin of bad regression bugs. – Leonid Shifrin Sep 09 '16 at 14:35
  • @matheorem First, it's just my opinion added to Albert's, there is no first principle here to decide what is right or wrong. Second, your suggestion may work fine if your default value makes sense and can be computed at definition time. The real advantage of Automatic is that it postpones the computation of the value of that argument until we are in the body of the function, where we can use other passed arguments as well as the results of computations with them. This makes this method very flexible, since decisions are made late. – Leonid Shifrin Sep 09 '16 at 14:41
  • Hi, @LeonidShifrin . Thank you. Sorry for late comment. But I found it is difficult to understand the " The real advantage of Automatic". Could you please give an example to show that Automatic can do something others can not do? – matheorem Sep 11 '16 at 13:45
  • @matheorem Somewhat artificial one, but code like this is actually used in real life. Here we will create a full name for a file, using either specific directory, if it is provided, or some base one, that is always provided as another argument: fname[baseDir_, name_, currentDir_:Automatic]:=FileNameJoin[{If[currentDir === Automatic, baseDir, currentDir], name}]. Here the final value for the directory is using both the value passed for currentDir, and in some cases, values passed for other arguments (baseDir here). You can't easily achieve this by other forms of argument-passing. – Leonid Shifrin Sep 11 '16 at 14:11
  • @LeonidShifrin, Thank you. I understand. – matheorem Sep 11 '16 at 14:48
9

I'd love to have a short syntax form for that. I'd use it more often:

similarWords[string_, n:(_|PatternSequence[]) ]:= Nearest[WordList[],string,n]
Kuba
  • 136,707
  • 13
  • 279
  • 740
  • Hi, @Kuba. I just realized that n___ is not equivalent to n:(_|PatternSequence[]), and n:(_|PatternSequence[]) is actually equivalent to Szabolcs's suggestion, right? – matheorem Sep 09 '16 at 13:15
  • @matheorem correct. n___ alows you to put there a sequence which will break Nearest. Thus not included in my answer. – Kuba Sep 09 '16 at 13:16
  • Now, I truly understand. Your solution is as good as Szabolcs', even neater. Only one downside, it will confuse novice and make him(or her) look up the doc for quite a while : ) I wish I could accept both : ) – matheorem Sep 09 '16 at 13:22
  • @matheorem I'd go with Szabolcs for readability. – Kuba Sep 09 '16 at 13:24
8

This seems to work:

similarWords[string_, n: Repeated[_,{0,1}]] :=
  Nearest[WordList[], string, n]
celtschk
  • 19,133
  • 1
  • 51
  • 106