Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a default value for a function parameter which depends of other parameter

Tags:

raku

I'd like to create an script which takes an input file and optionally an output file. When you don't pass an output file, the script uses the same filename as the input but with the extension changed. I don't know how to write a default parameter which changes the extension.

#!/usr/bin/env raku

unit sub MAIN(
  Str $input where *.IO.f, #= input file
  Str $output = $input.IO.extension("txt"), #= output file
  Bool :$copy, #= copy file
  Bool :$move, #= move file
);

Unfortunately that doesn't work:

No such method 'IO' for invocant of type 'VMNull'
  in block <unit> at ./copy.raku line 5

How can I do something like that?

like image 645
EZM Avatar asked May 27 '26 21:05

EZM


1 Answers

error message is less than awesome but program not working is expected because you have in the signature

Str $output = $input.IO.extension("txt")

but the right hand side returns an IO::Path object with that extension but $output is typed to be a String. That is an error:

>>> my Str $s := "file.csv".IO.extension("txt")
Type check failed in binding; expected Str but got IO::Path (IO::Path.new("file.t...)
  in block <unit> at <unknown file> line 1
>>> sub fun(Str $inp, Str $out = $inp.IO.extension("txt")) { }
&fun

>>> fun "file.csv"
Type check failed in binding to parameter '$out'; expected Str but got IO::Path (IO::Path.new("file.t...)
  in sub fun at <unknown file> line 1
  in block <unit> at <unknown file> line 1

Sometimes compiler detects incompatible default values:

>>> sub yes(Str $s = 3) { }
===SORRY!=== Error while compiling:
Default value '3' will never bind to a parameter of type Str
------> sub yes(Str $s = 3⏏) { }
    expecting any of:
        constraint

but what you have is far from a literal, so runtime detection.

To fix it, you can either

  • change to Str() $output = $inp.IO.extension("txt") where Str() means "accept Any object and then cast it to Str". So $output will end up being a string like "file.txt" available in MAIN.

    • similar alternative: Str $output = $inp.IO.extension("txt").Str but it's repetitive in Str.
  • change to IO::Path() $output = $inp.IO.extension("txt"). Similarly, this casts to whatever recieved to an IO::Path object, so, e.g., you'll have "file.txt".IO available in $output. If you do this, you might want to do the same for $input as well for consistency. Since IO::Path objects are idempotent to .IO (in eqv sense), no other part of the code needs changing.

like image 75
Mustafa Aydın Avatar answered Jun 03 '26 08:06

Mustafa Aydın