12 ms·
KW's: CopyFile(from: 'foo', to: 'bar') or JS: CopyFile({from: 'foo', to: 'bar'}) or as types: CopyFile(Source('foo'), Dest('bar')) or as fluent:
by thelazydogsback 7y ago
KW's:
CopyFile(from: 'foo', to: 'bar')
or JS:
CopyFile({from: 'foo', to: 'bar'})
or as types:
CopyFile(Source('foo'), Dest('bar'))
or as fluent:
Copy.from('foo').to('bar')
- toyg 7y agoor as Python: shutil.copy(src='foo', dst='bar') That's straight out of stdlib, btw.
- echelon 7y agoThose are keyword args and are not specific to just Python. :) The parent post outlines many techniques, including this. Some are more apt than others depending on your language of choice. I would argue that there's another option of passing a strongly typed struct for args, and that the JavaScript and Ruby solution of dicts falls between this and kwargs. I'm fond of properly designed fluent APIs, myself. While on the subject of homogenous argument types, especially bad are the APIs that not only accept the same types, but have order semantics that differ between functions within the same library. PHP used to be a notoriously bad example of this with respect to their needle and haystack search parameters. (I think they fixed it?)
- toyg 7y agoYeah, I was just showing that in Python keyword arguments are first-class in every respect, which is not that common. The "JS solution" for example is not a solution, but only a convention: the actual interface is just expecting an object, and there is no explicit check on the validity of such object or its contents. That is nowhere near "strongly typed", if anything it's weaker than average.
- thelazydogsback 7y ago> The "JS solution" for example is not a solution, but only a convention: the actual interface is just expecting an object, Well if it uses de-structuring syntax, then it can be type-checked.
- tomjakubowski 7y agoDe-structuring syntax isn't required, this is just as easily type-checked: interface CopyArgs { src: string; dst: string; } function copy(args: CopyArgs) { exec(["cp", args.src, args.dst]) }
- thelazydogsback 7y agoSure, if you want to get all interfacy - but pretty soon it'll be Java :)
- davedx 7y agoHah I thought that initially, but TypeScript interfaces are simpler (and usually co-sited) compared to their OO cousins. I find them a pleasure to use.
- RobertKerans 7y agoIt can, manually (as it can for a non-destructured object), but it has no typing guarantees - the function will still happily be interpreted and run as long as _any_ object is given as an argument.
- gnaritas 7y ago> I'm fond of properly designed fluent APIs, myself. Fluent API's are a symptom of a missing language feature, a cascade operator as found in Smalltalk. anObject methodOne: a; methodTwo: b; methodThree: c. ; does syntactically what returning this does in a fluent API without hijacking the return type of the method to do it and allows you to do it on any method any time without it needed to be designed for use that way.
- ambicapter 7y ago> (I think they fixed it?) No, they put out a conf talk that stated that there _was_ a logic to it, you just needed it to be explained to you.
- timw4mail 7y agoReally, most of the relevant PHP functions are just thin wrappers around the C functions with the same name. But nobody talks about how nonsensical C function names are.
- Waterluvian 7y agoI love this part of Python. I can choose to include the argument names if I think they tell a more clear story.
- jjoonathan 7y agoor as Objective C: [[NSFileManager defaultManager] copyItemAtPath:src toPath:target error:&error]; Inspired by smalltalk, of course. ObjC's verbosity is legendary, and I'm not its hugest fan, but there's a key innovation here that I am a huge fan of: the language forces other people to name their arguments, in much the same way that python forces other people to indent their code, even if they would otherwise default to making a hot mess of things.
- cat199 7y ago> otherwise default to making a hot mess of things. one can still make a hot mess of 1000-wide lines, bad variable names, and various other forms of spaghetti cowboy-ism.
- jjoonathan 7y agoYep, no doubt about it. My aim isn't to argue that forced named args will cure all that ails you, just to share that they "feel" quite different from optional named args, and that on the balance I felt they were a net positive.
- Scapeghost 7y agoThat verbosity isn't even a big deal as you write once, but read many times. One of the first things about Swift that I fell in love with was the difference between things like DrawRect(10, 20, 30, 40) in languages like C#, and DrawRect(at: origin, width: 30, height: 40) in Swift.
- naniwaduni 7y ago> That verbosity isn't even a big deal as you write once, but read many times. One of the most pernicious myths of software engineering is that it happens.
- Scapeghost 7y ago"Reading" code in this sense doesn't just mean explicit review by different people, but also as in reading it subconsciously as you scroll through your own code. If I see a chain of numbers like 1, 2, 3, 4 my brain has to recall the order of parameters or look it up. If I'm looking for rectangles that should be wider than they are tall, I have to keep thinking "the third number should be higher than the fourth number" Oh and I can't just Cmd+F "width:" or "height:" either.
- ww520 7y agoSwift has keyword parameter for function parameters, as in copy(from: "foo", to: "bar")
- goatinaboat 7y agoDid they fix the thing where the first argument can’t be named but the rest can? I kicked the tyres on Swift when it was new and that was a real turn-off.
- ww520 7y agoThe first argument can be named just fine. func fn1(myLabel1 myParam1: int, myLabel2 myParam2: int) { } fn1(myLabel1: 123, myLabel2: 456) func fn2(_ myParam1Omitted: int, myLabel2 myParam2: int) { } fn2(123, myParam2: 456)
- masklinn 7y agoYes, however that wasn't the case before SE-0046 (Swift 3): the early iterations had much stronger ties to objective-c where the method "names" the first parameter, so the first parameter could not be labelled separately. From Swift 3 onwards, parameters and their labelling is completely consistent. GP is wrong in one place though (at least for swift 2, not sure for swift 1): you could label the first formal parameter explicitly, it just wouldn't be labelled by default (unlike other parameters). That is: func f(p1:p2:) would define f(_:p2:) but func f(p1 p1:p2:) would define f(p1:p2:) Also, just to make things weirder, this special-casing would not apply to initialisers.
- masklinn 7y agoThat was fixed in Swift 3.0 following the acceptation of SE-0046. In Swift 1.0, this applied to methods but not functions as it was following up from Objectice-C (foo:bar:baz: would become foo(_:bar:baz)), in Swift 2.0 it was expanded to all callables, and in Swift 3.0 it was removed and to be specified explicitly making labelling completely consistent.
- yellowapple 7y agoOr sidestep this problem entirely by being explicit: contents = ReadFile('foo') WriteFile('bar', contents) Or more concisely: WriteFile('bar', ReadFile('foo')) (Of course, this would likely preclude the ability to do some means of copying that doesn't involve reading the whole file into memory).
- jiggawatts 7y agoPlease don't ever recommend such code, even in jest. This is Wrong, with a capital W. The file copy API is fundamentally different to reading and and then writing the contents of a file. The "copy" system call on most platforms also: - Copies metadata such as attributes. - Copies alternate data streams. - Preserves advanced features such as sparse files, compression, encryption, BTRFS/ReFS/ZFS integrity settings. - Can copy externally archived files without restoring them. - Can copy files server-to-server without transferring the data to the client computer. - Copies block-by-block instead of all at once, allowing files to be copied that do not fit into memory. - Copies asynchronously so that reads and writes are overlapped - Etc...
- viraptor 7y ago- can be almost instant and copy only metadata on CoW filesystems
- bluGill 7y agoIf your file operations are designed for it they can recognize the copy situation above and fix it. I don't think it is practical to implement it, but I can design a toy language that would do the above. (Toy because to make it possible to do the above I would have to compromise other things wroth having)
- yellowapple 7y agoThere are lots of usecases where the things you mention are either not important or outright bad. My code also allows for extra data processing steps in between should the use case evolve beyond mere copying. The code above could, however, just deal with references to some file handle and not actually read or write the contents (though ReadFile() or WriteFile() would be less-than-ideal names for such functionality).
- reifiedgent 7y agoor C#: CopyFile(src: "foo", dest: "bar");
- keithnz 7y agothis doesn't really solve the problem, its a little better, however, as it STILL accepts positional arguments you can still get in trouble if you don't use parameter names.
- alkonaut 7y agoA compile time analyzer that says "always use named parameters when calling functions with similar type of consecuitve arguments" should be pretty easy to write. It would be better to have a foolproof API, but obviously it can't change now.
- gnud 7y agoc# already has the 'chained' API in the standard library: new FileInfo(srcPath).CopyTo(dstPath) On the other hand, it also has the more error-prone File.Copy(sourceFileName,destFileName);
- Lio 7y agoI know nothing about C# so please pardon my ignorance but how does: new FileInfo(srcPath).CopyTo(dstPath); provide more protection vs CopyFile(src: srcPath, dest: dstPath); Assuming that both path strings are set correctly isn't a typo here going to cause the same error and be just as visible? e.g. new FileInfo(pathA).CopyTo(pathB); CopyFile(src: pathB, dest: pathA); neither would seem to show which is correct without knowing what the path values are if the variable names are ambiguous.
- gnud 7y agoWhen using the FileInfo api, it's obvious which argument is the source and which argument is the destination. When using the CopyFile, it's not. If you can't keep track of your variable names, then using keyword arguments, like others here talk about, won't help much, either.
- gregmac 7y ago> Copy.from('foo').to('bar') Funny, I don't think I've actually seen a fluent file API like this before. I guess most languages have file I/O built in to their standard libraries. My immediate reaction to this, though, is that you could make it more general: File('foo').CopyTo('bar'); You could then also have .ReadAsText() .MoveTo(target) .SetOwner(owner) .GetFullPath() This is readable and solves the problem the original post is discussing, but it's also nice when learning because an IDE can give very useful help about possible operations, and all you have to remember is the File() function.
- anonytrary 7y ago> also nice when learning because an IDE can give very useful help about possible operations Why is chaining necessary for this? On-instance tab-autocomplete list-all on-tab accomplishes the same thing.
- skrebbel 7y ago> On-instance tab-autocomplete list-all on-tab I have no idea what you just said.
- nurettin 7y agoI think it means: (While editing code, position the cursor) on (a class) instance (and press) tab (to swith to) autocomplete (mode and) list all (members of that instance by clicking) on tab (again).
- coldtea 7y agoHe said that clicking TAB on an instance in an IDE (e.g. foo.[TAB]) can list all possible operations available on that instance -- in other words, you don't need a fluent interface for this.
- stouset 7y agoYour suggestion is basically Ruby’s `Pathname` class (sans convenient copy method).
- jez 7y agoThe thing I like the most about the "as types" approach is that once the thing is wrapped, passing it around has zero boilerplate. In this program below, the only place that suffers from having created a newtype for the strings is the place where they're created (parseArgs) and where they're used (copyFile). None of the other functions (main, helper1, helper2) ever need to peek inside the type, so there's no boilerplate but still all the type safety. newtype Source = Source Filename newtype Dest = Dest Filename -- Boilerplate at usage copyFile :: Source -> Dest -> IO () copyFile (Source from) (Dest to) = ... -- Boilerplate at creation parseArgs :: IO (Source, Dest) parseArgs = do [arg1, arg2] <- getArgs (Source arg1, Dest arg2) -- Helpers with NO boilerplate helper1 :: Source -> Dest -> IO () helper1 from to = copyFile from to helper2 :: Source -> Dest -> IO () helper2 from to = helper1 from to main :: IO () main = do (from, to) <- parseArgs helper2 from to And by "all the type safety," I mean it's a type error to accidentally swap the args incorrectly to the function call: (from, to) <- parseArgs helper2 to from
- raverbashing 7y agoYes but creating the scaffolding is the hard part and getting to the "once the thing is wrapped" takes more time than the other solutions. You could "force" parameter naming, and it would be easier. Types are good but having 10 types of string doesn't make sense.
- eterps 7y ago> Types are good but having 10 types of string doesn't make sense. Not if the strings are clearly distinct (like a name being different from an e-mail address).
- louthy 7y ago> Types are good but having 10 types of string doesn't make sense. It really does make sense. Types like string, int, float, etc. are poorly defined for most things they represent. There's no context and are just one step away from being dynamic, like values in a dynamic language. By enforcing a contextual contract (perhaps with additional constructors that constrain the value) you leverage the type-system to do the hard work of checking for stupid mistakes. On large code-bases this reduction in cognitive complexity makes a whole class of bugs go away. So, 100s of types of string could make sense, it really does depend how many types of thing you have which are distinct and can be represented as a string. But once they're defined, they're not strings any more, they have an internal state which is a string, yes, but their type is not a string.
- GarvielLoken 7y agonew File().Source("xyz").CopyTo("zyx") is the cleanest I believe, and also the most (real) OO way.
- DarkWiiPlayer 7y ago> and also the most (real) OO way. No. Far from it. First of all, what is File in your example? a class? an object? and what does File().Source() return? an object? what does that object represent? If you want it to be OO, you have a File object, to which you send a "copy" message. Depending on how strictly OO you want it to be, it then returns the new file object (or a reference to it).
- elygre 7y agoIn the fluent scenario, I will be waiting for the instruction to execute: Copy.from('foo').to('bar').execute(); Because of variations like these: Copy.from('foo').to('bar').owner('me'); Copy.to('bar').from('foo');
- friend-monoid 7y agoAs C: struct copy_args_s { char * src; size_t src#; char * dest; size_t dest#; }; int copy(struct copy_args_s args) { ... } int main() { copy((struct copy_args_s){ .src = "foo", .src_len = 3, .dst = "bar", .dst_len = 3 }); } Or with some macro magic: struct copy_args_s { char * src; size_t src#; char * dest; size_t dest#; }; int copy(struct copy_args_s args) { ... } #define copy(...) copy((struct copy_args_s){__VA_ARGS__}) int main() { copy(.src = "foo", .src_len = 3, .dst = "bar", .dst_len = 3); }
- coldpie 7y ago> Or with some macro magic Stop! Put down the editor and step away from the compiler, please. No one wants to get hurt.
- kragen 7y agoI agree that named parameters are a better solution in this case. A problem I have with the types solution is that, although it's easy to tell if the code is wrong, it's often a bit of a puzzle in my experience to figure out how to write it in the first place: "Hmm, this takes a Foo and a Bar. Can I construct a Foo from a string? No, but I can construct it from a Baz or a Quux. Can I construct a Baz from a string? No, just an int. How about a Quux? Yeah, there we go. Now, how about that Bar?". And I've seen other people have that problem too, especially novice programmers. I'm not a novice but it's still a problem for me in a new type-heavy API. I realized I was creating that problem in Yeso, and I couldn't figure out how to simplify the API, so in addition to starting the documentation with complete examples, I made a graph with graphviz that shows the type structure of the API: https://gitlab.com/kragen/bubbleos/blob/master/yeso/README.md#calling-interface-diagram https://gitlab.com/kragen/bubbleos/blob/master/yeso/README.m... I don't have that problem with named-parameter interfaces (I just look at the list of methods and parameter lists), and maybe I could run into it with fluent interfaces in theory, but in practice that hasn't been much of a problem in my experience. The article author was writing in Golang, which doesn't have named parameters, but in languages like Python, Lua, Clojure, or, as you say JS, named parameters are straightforward. In Lua it's even simpler than in JS: copy {from='presentation.md', to='/tmp/backup'} I would never claim that Lua isn't bug-prone, though!
- ulucs 7y agofor things that have clear direction, 'foo' `copyTo` 'bar'
- GordonS 7y agoYou can get something like your last example in c#: ```c# var source = new FileSystemFile("source"); source.CopyTo("target"); ``` Of course, this isn't the only way to do it; you get the usual footguns as described in the article too. Also apologies if the formatting is crappy - on mobile, and TBH not sure what markdown-like syntax HN supports, despite being a long-time user!