Reputation: 863
Just kicking the tires of Java 8, and noticed Lamdas and streams functional programing.
Was wondering if a simple command line args consumer could use streams.
Cant figure out howto get at two stream elements at once however... Plus below I'd need to handle args that do and dont take values, so couldnt do any odd/even trickery. Would have consider args to always start with a dash, and optional values never do.
String[] args = ("-v", "-c", "myconfigfile", "-o", "outputfile");
Arrays.toList(args).stream().map( a,v -> evalArg(a,v));
public static void evalArg(String arg, String val) {
switch(arg) {
case "-v":
verbose = true;
break;
case "-c":
config_file = val;
break;
case "-o":
output_file = val;
break;
default:
System.err.println("unknown argument " + arg + " " + val);
break;
}
}
Upvotes: 3
Views: 2266
Reputation: 977
If you have key-value pairs then you can use following:
public static void main(final String[] args) {
String[] args = {"-v", "value", "-c", "myconfigfile", "-o", "outputfile"};
pairStream(Arrays.asList(args), (param, value) -> param + ": " + value)
.forEach(System.out::println);
}
public static <X, Y> Stream<Y> pairStream(List<X> list, BiFunction<X, X, Y> mapper) {
Supplier<X> s = list.iterator()::next;
return Stream.generate(() -> mapper.apply(s.get(), s.get()))
.limit(list.size() / 2);
}
// Result:
// -v: value
// -c: myconfigfile
// -o: outputfile
Upvotes: 2
Reputation: 1781
You seem to have the idea, but I thought I would expand on it with a little additional data.
The concept of a data stream is probably older than Java; processing them goes back at least to processor architectures. CPUs are built with something called Von Neumann Architecture; which allows the processor to maintain pointers to different parts of memory and, basically, have access to all of it all the time. Some other processors in common use (such as GPUs) are stream processors. They handle one operation at a time, and outside of an occasional (and often dubious) clever trick, have no knowledge of any other part of the stream. This allows for optimal parallel processing, among other things, and is precisely why GPGPU can be blisteringly effective for some tasks, but is woefully ineffective at running an entire machine.
The Streams API allows you to run operations under the same circumstances. The only way to operate on more than one item at once is through a reduce; which is basically the construction of another stream. Through it, in theory, you can certainly scan arguments and launch options; but due to the potential redundancy, that doesn't mean it's the best in practice.
Upvotes: 0
Reputation: 4735
After writing my original answer I realised this is possible using reduce, for example like this:
String[] foo = {"-t", "-c", "myconfigfile", "-o", "outputfile"};
Arrays.stream(foo).reduce((arg, val) -> {
switch (arg) {
case "-v":
verbose = true;
break;
case "-c":
configFile = val;
break;
case "-o":
outputFile = val;
break;
// Non-exhaustive
}
return val;
});
My original answer, using state and function objects:
Remember that Java does not actually support first class functions. When you are using a lambda, you're actually passing an object containing a function. These objects can have state. You can use this to your advantage, so you should be able to do something like this:
String[] foo = {"-c", "myconfigfile", "-o", "outputfile"};
Arrays.stream(foo).forEachOrdered(new Consumer<String>() {
String last;
public void accept(String t) {
if (last == null) {
last = t;
} else {
System.out.println(last + " " + t);
last = null;
}
}
});
Whether this is a good idea is a different consideration though. Note the use of forEachOrdered as plain old forEach is not guaranteed to go through the list in a specific order. Also note that map would not work for doing one thing with two elements, as a mapping function has to take one argument and return one result, resulting in a one-to-one relationship between the input stream and the output stream.
For your specific example you would have to do something like this though:
String[] foo = {"-t", "-c", "myconfigfile", "-o", "outputfile"};
Arrays.stream(foo).forEachOrdered(new Consumer<String>() {
String arg;
public void accept(String val) {
if (arg == null) {
arg = val;
} else if (t.startsWith("-")) {
System.out.println(arg);
arg = val;
} else {
System.out.println(arg + " " + val);
arg = null;
}
}
});
A third alternative is of course to do as yshavit suggests and not use streams at all:
LinkedList<String> argList = LinkedList<>(Arrays.asList(args));
while(!argList.isEmpty()) {
switch (argList.pop()) {
case "-v":
verbose = true;
break;
case "-c":
configFile = argList.pop();
break;
case "-o":
outputFile = argList.pop();
break;
default:
System.err.println("unknown argument " + arg + " " + val);
break;
}
}
Upvotes: 0
Reputation: 43436
There's no easy way to do this with lambdas/mapping. Not all forms of iteration lend themselves to mapping a single lambda.
For instance, the functional approach to this probably wouldn't use lambdas; it would use recursion, with each step optionally popping more args if it needs to. Something like (in pseudo-code, and ignoring error checking, etc):
Options[] parse(args : String[]):
if args.isEmpty:
return []
currentOpt, remainingArgs = args.pop()
case currentOpt of:
"-v":
return [ Verbose ] ++ parse(remainingArgs)
"-c":
configPath, remainingArgs = remainingArgs.pop()
return [ Config(configPath) ] ++ parse(remainingArgs)
"-o":
outputPath, remainingArgs = remainingArgs.pop()
return [ Output(outputPath) ] ++ parse(remainingArgs)
_:
return [ UnknownOption(currentOpt) ] ++ parse(remainingArgs)
Upvotes: 0
Reputation: 393
Stream API is not really built to process values which depent on the state of another value. The used lambda function should be stateless (see also the Java documentation: Stream#map()
). However it is possible to use sequential()
on the stream to ensure the correct order and allow using a state inside the function used to process the elements, but that is not really recommended.
Better use a library for parameter parsing like Apache Commons CLI.
Upvotes: 1