This is not entirely true, and not just being pedantic. “Windows” always recognises back-slash as the separator and sometimes recognises forward-slashes. The sometimes can bite in such unexpected ways that I tend to think “just use forward-slashes” does more harm than good:
# Sys.chdir {|C:\Windows|};;
- : unit = ()
# Sys.command {|System32\where.exe where|};;
C:\Windows\System32\where.exe
- : int = 0
# Sys.command {|System32/where.exe where|};;
'System32' is not recognized as an internal or external command,
operable program or batch file.
- : int = 1
# Sys.command {|./System32/where.exe where|};;
'.' is not recognized as an internal or external command,
operable program or batch file.
- : int = 1
# Sys.command {|C:System32/where.exe where|};;
C:\Windows\System32\where.exe
- : int = 0
This is not a contrived example - it happens very easily in the compiler’s own build system. Same effect with the higher-level Unix functions such as Unix.open_process_in, and it’s not a bug in either Windows or OCaml.
In general, if Windows needs to be supported, I’d support it properly - which means letting go of two key Unixisms right at the start. Firstly, there is not one directory separator, there are two (which Filename.dir_sep alas gets wrong) and there is no “root” directory. My own experience is that if whatever is being written starts with those two assumptions, then mostly the backslash-slash/forward-slash detail doesn’t get in the way.
Note that Windows supports either the forward slash (which is returned by the AltDirectorySeparatorChar field) or the backslash (which is returned by the DirectorySeparatorChar field) as path separator characters, while Unix-based systems support only the forward slash.
So we might need something like Filename.alt_dir_sep which would be forward-slash on Windows?
To clarify, the only parts of Windows that can’t handle forward slashes are some legacy CLI parsers, most notably the one in cmd.exe. The underlying Windows APIs have supported forward slashes since DOS.
Which is why all your examples involve cmd.exe, including Unix.open_process_in. PowerShell for example handles them correctly. I’d argue that running commands through a shell is the Unix-ism here.
I think you’re missing my point, which is that the advice “use forward slashes” in practice results in worse problems further down the line, and portability is better achieved by embracing the differences up-front, rather than having a whole list of things to remember to do, some of which one’s users also have to remember to do!
If we’re in the business of clarifying, the Windows of today isn’t, and never has been, DOS! This also isn’t true - cf., for example, LoadLibrary:
When specifying a path, be sure to use backslashes (\), not forward slashes (/).
(again, not something that gets hit every time - my entire point is that most of the time just using forward slashes will work… until you hit the nasty-and-incredibly-hard corner case to debug where they don’t)
Those are the actual “high-level” portable functions! Sys.command is even just a wrapper around a CRT function (see _wsystem which, according to my copy of the sources, was written in 1993 for Windows NT, based on the 1985 implementation of system which was actually written for DOS… i.e. calling the shell is not a Unixism)
MSDN helpfully keeps all the old versions of the docs - System.IO. Path.AltDirectorySeparatorChar has been there since the start, but I think that suggests why we’d want something different in OCaml (2001 is well before the rest of the world properly woke up to the joys of functional programming!)
I’d prefer a function, e.g.
let is_separator =
if Sys.win32 then
function
| '/' | '\\' -> true
| _ -> false
else
Char.equals '/'
(the actual implementation in Filename would be different)
Maybe my comment came out wrong! I didn’t miss your point and, to your point, Root Local Device paths aren’t even canonicalized at all, so any code dealing specially with paths should, of course, avoid brittle shortcuts.
But Unix-heavy communities, like OCaml’s, are also prone to misunderstandings about Windows, so I wanted to point out to other readers that it isn’t a pervasive problem; you can get away with forward slashes very often, just not always. I’ve even catched Microsoft software using mixed slashes.
But yes, the solution with less edge cases is the better one!
And the standard C:\\Users\... paths are not NT paths, either. Indeed, the underlying NT kernel uses a much more narrow path format; this conversion is performed by the Windows APIs. The link above documents this in much more detail than Microsoft’s own docs.
Well spotted! But it does actually work. That must have been added to the docs to appease some customer trying to mix forward slashes with Root Local Device paths or something.
Here I do disagree. system being added early on for feature parity doesn’t mean that shelling out through cmd.exe, as opposed to creating a process directly, is idiomatic in Windows. Shelling is useful in Unix because you get to customize your .bashrc and export full shell commands as environment variables for software to call back. On Windows, I’d say shelling is just a worse CreateProcess.