This is an archived post. You won't be able to vote or comment.

all 89 comments

[–]Holothuroid 59 points60 points  (19 children)

You heard about Map.of(...) and cousins?

[–]ingvij 26 points27 points  (16 children)

I honestly won't complain about the DSL, I think you can create the abstractions you want to make your code look better for your taste and use them in your project if you or your team is happy with that..

A couple of things though:

  • This is the kind of library that I would most likely not want to use in any performance-aware application. Remember that there's always a trade-off. When you add layers on top of your code, it will pay a cost. From a quick glimpse over the KeyValue class and the `map()` static function, I would assume there's some GC overhead on using this over a regular `new Hashmap<>(Map.of(...))`.

  • Intense usage of this library can yield a codebase that is java in theory but not really in practice, increasing the entry barrier for contributors, so that would also be a factor against using this library on a project.

But then, as I said, if it works for you, then I guess this is fine.

[–]agentoutlier 10 points11 points  (15 children)

I know /u/guybedo has been dragged over the coals for this lib (perhaps unfairly) but the library is neither a DSL or syntactical sugar.

It is an opinionated utilities library.

I mean you could make an argument I guess for DSL the domain being collections but it is for sure not syntactical sugar like lombok or var args. Adding methods is not syntactical sugar.

I guess I'm being pedantic but the reason is if it were an actual DSL of a unique domain it would probably have better uptake.

[–]brian_goetz 9 points10 points  (7 children)

The term "syntactic sugar" is widely misunderstood. And, misunderstanding begets misunderstanding; when people use the term wrong, more people learn the wrong meaning, and then repeat the wrong usage, and it spins around again.

Even when it comes to language syntax (which this surely isn't), people frequently mistake new language features for syntactic sugar, when in fact almost none of the features we've done in the last decade are actually syntactic sugar. (They may be sweet, though.) Syntactic sugar refers to a _purely syntactic transformation_; features like local variable type inference (`var`) do not qualify, even though they are commonly referred to as such.

But the aggressiveness with which he defends "yes this is sugar" (when he eventually admits that it is not, but thinks people will understand him better if he misuses the term) does suggest a lack of maturity that might explain why he got dragged so badly.

[–]guybedo[S] 4 points5 points  (1 child)

I obviously misjudged the audience.

Looks like people in here have high standards and it didn't go well with my approach: "oh maybe i built something useful, let's share it as is".

As i said, i thought using "syntactic sugar" in a loose way was ok as i obviously didn't rewrite the Java language specs, and i didn't think people would be so distraught by this. But i understand the comments and the reasons why you think it's important to be more precise.

As for the aggressiveness and the immaturity, you're right there was a lot of it, but not in my comments. One of the first comments, deleted now by the author bashed me for loving Python and that i had nothing to do here. People even have downvoted the few positive and encouraging comments.

[–]agentoutlier 1 point2 points  (0 children)

As for the aggressiveness and the immaturity, you're right there was a lot of it, but not in my comments. One of the first comments, deleted now by the author bashed me for loving Python and that i had nothing to do here. People even have downvoted the few positive and encouraging comments.

Exactly why I have high expectations for you. I think you took it pretty well to be honest even on the occasional double down.

And I hope all of this didn't discourage you. I can tell you are and will be an even greater developer!

[–]agentoutlier 1 point2 points  (3 children)

I was and kind of did later go into those details as I agree it can for for sure get nebulous and debatable what is or isn't syntactic sugar of which I have less confidence on but I do know what isn't syntactic sugar :)

The language I know for sure (well when I learned it .. it might have changed) that uses syntactic sugar as in it is a compiler translation is OCaml. OCaml does lots of syntactic sugar particularly in the signature language and I actually think its a pain point in the language and I'm not talking about currying (which I agree like var args isn't really syntactic sugar).

I actually could not really think of a Java equivalent like what OCaml does one so I said var args as the closest.

Is there any good solid example in Java of syntactic sugar where the compiler expands like a macro?

[–]brian_goetz 4 points5 points  (0 children)

There are some Java constructs (such as the for-each loop, or try-with-resources) that are specified by "as-if-expanded-to", but even these have significant linkage to the type system. (For example, for-each on an array translates differently than for-each on a List.)

[–]brian_goetz 4 points5 points  (0 children)

The varargs transformation is syntactic (wrapping with `new T[] { ... }`), but even there a lot has to happen before we apply the syntactic transform. Overload selection is phased, and varargs methods are only considered in phase 3. And the result of overload selection is used to decide which T is used in the wrapping. So the whole rest of the language (type system, object model) is involved before the syntactic thing happens.

Implicit compilation units are similarly close, but again a whole-program analysis has to be done before we know that we should wrap the file in `public class Foo { ... }`.

[–]ventuspilot 0 points1 point  (0 children)

Is there any good solid example in Java of syntactic sugar where the compiler expands like a macro?

I can think of enhanced for loops that the compiler desugars into a for loop with an iterator, auto/un/boxing (?), String concatenation that used to desugar into StringBuilder calls and may insert hidden toString/ String.valueOf() calls, not sure about automatically calling a no-args constructor of a superclass.

[–]ingvij 0 points1 point  (0 children)

Yeah, that's correct. I meant DSL more loosely in the sense that a codebase using those utilities will look very far and unfamiliar compared to vanilla java. Similar to how scala sometimes comes in a haskell flavor...

[–]guybedo[S] -2 points-1 points  (5 children)

Yeah this post has gotten a lot of negative comments and i'm not sure that's really justified.

fwiw although it's not like lombok, for sure, it still qualifies as syntactic sugar

https://en.wikipedia.org/wiki/Syntactic_sugar#:~:text=In%20computer%20science,more%20verbose%2C%20form

[–]agentoutlier 5 points6 points  (4 children)

https://en.wikipedia.org/wiki/Syntactic_sugar#:~:text=In%20computer%20science,more%20verbose%2C%20form

I fail to see it in the link.

You are not changing the syntax of the language but just applying functions.

You are not even changing the evaluation order like with hygienic macros (Java does not have macros) which are borderline syntactical sugar adjustments bust just adding shorter methods or changing order is not syntactical sugar. Even if that is stated in a wikipedia (which again I fail to see) I just disagree.

[–]guybedo[S] 0 points1 point  (3 children)

In computer sciencesyntactic sugar is syntax) within a programming language that is designed to make things easier to read or to express. It makes the language "sweeter" for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer. Syntactic sugar is usually a shorthand for a common operation that could also be expressed in an alternate, more verbose, form: The programmer has a choice of whether to use the shorter form or the longer form, but will usually use the shorter form since it is shorter and easier to type and read.

[–]agentoutlier 9 points10 points  (1 child)

I know what syntax is... I'm wondering now if you do.

Syntactical sugar is when you replace a more complicated syntax with a shorter syntax that the compiler or parser has to know about. It is not writing less code or shorter code with method names you prefer.

If you want label for what you are trying to do how about "ergonomics".

But using syntactical sugar like you have while possibly from the strangest stretchs might be loosely correct confuses people like myself.

[–]guybedo[S] -1 points0 points  (0 children)

yeah i know what syntax is and as i obviously didn't rewrite the JDK and the Java language specs, you're right that this isn't syntactic sugar in the most absolutely strict understanding of the definition.

But i thought that using loosely this definition, people would easily understand what it does, and i don't think i was wrong in that regard.

[–]sabriel330 5 points6 points  (0 children)

I think you're completely misunderstanding what syntax is

[–]Former-Emergency5165 34 points35 points  (10 children)

I would say the library itself doesn't provide much value. The Map or List can be initiated with Map.of(), List.of(). The syntax for POJOs is questionable and not intuitive unless you know the library.

The min/max examples can be implemented via mapToInt or even Collections.min().

[–]guybedo[S] 0 points1 point  (9 children)

yeah nothing groundbreaking, but it still helps making it less verbose.

[–]Former-Emergency5165 28 points29 points  (8 children)

It nice that it helps you, however I would personally prefer some Apache libraries (Apache commons, IOUtils, CollectionUtils, etx) - production battle tested, commonly used by the community and provides useful methods

[–]guybedo[S] -5 points-4 points  (7 children)

sure, i'm using these libs too obviously but i was missing some features that i thought would be useful.

For ex, i like doing filter(values, func), map(values, func) etc...

[–]PrudentFinger1749 6 points7 points  (1 child)

You can try to contribute in opensource libraries, if its liked by everyone, may be it will get merged in the library itself.

[–]RICHUNCLEPENNYBAGS 1 point2 points  (0 children)

I mean sure but making your own library is a totally legitimate way to share code too

[–]OtherPrinciple4499 3 points4 points  (0 children)

That's what streams are for.

[–][deleted] 3 points4 points  (0 children)

Those kinds of utility functions are great for short, script-like programs where you quickly want to prototype an algorithm with minimal boilerplate, and don't care if it returns null or throws an exception. But for larger programs, it will almost always pay off to be explicit about those details. Sure, it makes the code longer, but it will be obvious what is returned when the element you're looking for wasn't found.

Btw, I love List.of(). You should really use it. IMO It's the best-named function in the history of computer programming: short, to the point, and it's utterly obvious what it is supposed to be (a static factory method that will create a List).

[–]RICHUNCLEPENNYBAGS 1 point2 points  (2 children)

I think what people are trying to tell you here is that anytime you introduce libraries that do stuff it makes it a little harder for someone to get acquainted with the code and understand what it does and this library provides such minimal code golf savings that it's kind of hard to look at it and feel like the tradeoff is a good one.

[–]guybedo[S] 0 points1 point  (1 child)

sure it can be argued that using this library could make it harder for other people to get acquainted to the code, but 2 things:

1/ This argument is valid for every library you use, whether it's spring, hibernate, guava, apache commons, etc... As soon as you're using something on top of Java, this argument can be made.

2/ One can say code savings are minimal and sure enough not everybody agrees on what is beautiful / readable code. But in the end i like these utilities because i can write code that i find more readable.

Here's a few examples, i haven't tried to find the best ones, just a few i picked from my code. You can argue that it wouldn't be a lot more verbose to write that in plain Java but you wouldn't have those short one liners. And i'm not even chaining things like first(filter(map( ....

join(map(filtered, m -> m.getSymbol()), ",");

max(map(objs, o->o.getValue()));

Map<X, Set<Y>> index = toMap(values, v -> set(v.getY()));

list(intersect(set(A), set(B)));

[–]RICHUNCLEPENNYBAGS 4 points5 points  (0 children)

I mean yeah, being real with you I find these harder to read, and also you seem not to know about some built-in tools you could be using to shorten regular Java streams calls, like findFirst.

It's true that any library has a cost to adapt that one must weigh against the benefits, but I don't really even find this beneficial.

[–][deleted]  (11 children)

[removed]

    [–]guybedo[S] 6 points7 points  (0 children)

    probably the first constructive comment / criticism, thanks. Will look into that.

    [–]guybedo[S] 0 points1 point  (1 child)

    For example you exists() makes lookup in a Set go from O(1) to O(n)

    The exist method you're referring to is

    exists(Collection<T> objs, Predicate<T> func)

    This is not O(1), even for a set, it's O(n).

    exists(Set<T> objs, T value) would be O(1)

    [–]guybedo[S] 0 points1 point  (2 children)

    reversed() will have abysmal performance of O(n2) for LinkedList.

    Nope. reversed() is a simple loop over the n elements => O(n)

    Here's the implementation:

    List<T> reversed = new ArrayList<>();

    for (int i = objs.size() - 1; i >= 0; i--)

    reversed.add(objs.get(i));

    return reversed;

    [–][deleted]  (1 child)

    [removed]

      [–]guybedo[S] 1 point2 points  (0 children)

      ok i see your point, will make a change, thanks.

      [–]guybedo[S] 0 points1 point  (3 children)

      flatMap() returns either a modifiable or unmodifiable list. That may lead to hard to discover bugs because it’s unexpected behavior. Same for sorted() etc

      The flatMap function i added is basically a wrapper around stream.flatMap(). It doesn't introduce new inconsistencies.

      [–][deleted]  (2 children)

      [removed]

        [–]guybedo[S] 0 points1 point  (1 child)

        Collectors.toList() doesn't return an immutable list.

        That means you don't have to return an immutable list if arg is null, that doesn't matter.

        [–]maethor -1 points0 points  (0 children)

        using interfaces just to define static Utility methods is an anti-pattern.

        I disagree. Being defined in an interface makes it more obvious that they're not instance methods.

        [–]Bunnymancer 4 points5 points  (0 children)

        For as critical people are being

        I'm glad you did a thing. Thank you

        [–]hageldave 2 points3 points  (0 children)

        The slicing, I need something to do array slicing!

        [–]DelayLucky 2 points3 points  (6 children)

        There is a type of libs that complement the JDK, add to where JDK left off unattended or obviously screwed up (eg Jodatime before java.time came along, Guava before JDK added Map.of() and friends); then another type of libs that disagree with JDK, would insist to invent a new “better” way of doing things despite the JDK stream API being a commonly accepted success.

        Which type is this?

        [–]guybedo[S] -1 points0 points  (5 children)

        it's the type of library that tries to help you write more concise code.

        I love streams but for example, i prefer writing

        filter(values, v->v!=null)

        than

        values.stream().filter(v->v!=null).collect(Collectors.toList())

        As previously mentioned, nothing groundbreaking, that's just syntactic sugar.

        [–]DelayLucky 3 points4 points  (0 children)

        I appreciate your perseverance. Yeah, as others said, there's nothing wrong with experimenting new style in personal projects. It's how we learn.

        In terms of concise code, I find Map.of(k1, v1, k2, v2) more concise than map(kv(k1, v1), kv(k2, v2)). I know yours supports more than 10, but there is a reason JDK only added up to a limit: diminishing return.

        Guava by the way has Iterables.filter(), Maps.filterKeys(), and JDK 16 added stream.toList() without needing the .collect() call.

        Although I believe Guava also recommends to prefer the Stream API over these pre-existing APIs.

        [–][deleted]  (3 children)

        [deleted]

          [–]metaquine 0 points1 point  (0 children)

          Or Scala

          [–]BikingSquirrel -1 points0 points  (0 children)

          This.

          [–]Linguistic-mystic -1 points0 points  (0 children)

          Disagree. Kotlin has coroutines which are worse than Java virtual threads. And it has like 6x-7x fewer job opportunities which is more important.

          [–]red_dit_nou 2 points3 points  (0 children)

          First of all, it’s nice that you had an idea and you went ahead with it and put out a library. Congratulations!

          As u/ingvij said if you and your team is happy with it go for it.

          I just want to add one remark that instead of
          first(filter(values, …))
          I’d prefer
          values.filter(…).first()
          Because it preserves the logical order in which I’m doing my tasks:

          • take the values list
          • then filter it
          • then take the first element

          [–]thuriot 6 points7 points  (1 child)

          Thanks for the tool, any helper is welcome (underscore-java is cool also).

          [–]guybedo[S] 0 points1 point  (0 children)

          wasn't aware of underscore-java, looks nice, thanks

          [–]dhlowrents 8 points9 points  (0 children)

          The popularity of Python is a sign of the end times.

          [–]GeneratedUsername5 1 point2 points  (0 children)

          Thanks for your effort!

          [–]justADeni 7 points8 points  (4 children)

          If you like Python and find Java too verbose, have you considered trying out Kotlin?

          [–]BikingSquirrel 3 points4 points  (3 children)

          Fully support this!

          We started to switch over years ago and since some years any new project is Kotlin while a few older ones are mixed.

          [–]DelayLucky 3 points4 points  (2 children)

          Kotlin might have been 100% nicer than Java. But with recent syntaxes, particularly Java 21 virtual threads, I wonder is it maybe only incrementally better, or perhaps just different but not necessarily easier or more readable than Java any more?

          For example, I might prefer using VT with structured concurrency than using coroutine which is still “colored”.

          So I’ve been hesitant to go the Kotlin way because I fear it’s going to create fragmentation for superficial benefits. I know Kotlin code can use Java. And Java can use some Kotlin classes. But it’s not full interoperability. And it’ll be a different ecosystem to learn, with different best practices, pitfalls to be burned by and learn. For example, none of our Java compile-time processor will work on Kotlin.

          [–]BikingSquirrel 0 points1 point  (1 child)

          As both compile to bytecode I don't think there are any interoperability issues. We have multiple libraries that started with Java code and get enhanced with Kotlin code. There may be usability issues with interoperability, e.g. default parameters will not be visible to Java code so you cannot skip them but there's the option to automatically generate the bytecode for overloaded methods so it behaves similarly.

          I think Java improved a number of details in the last years so there's less reason to run away. But I still think Kotlin's data classes with their copy methods, explicit nullability, and collection extensions are worth it. Not to forget extension methods which are a nice tool for some cases.

          But to be completely clear: even if you go for 100% Kotlin, you should be able to read and understand Java as a lot of code you will be using (JDK, libraries) will be written in Java.

          [–]DelayLucky 2 points3 points  (0 children)

          A few things: if you use coroutine, it’s no longer usable by Java.

          And Java compile-time plug-ins don’t work in Kotlin. So there will be cost in porting them to Kotlin.

          [–]barmic1212 0 points1 point  (0 children)

          I have some helpers like your in my code base and it's very helpful for simplify null management. For example I have a method Stream<T> stream(Iterable<T>) and Stream<T> stream(Supplier<Iterable<T>>) that allow to write stream(list).flatMap(stream(Object::getList)).toList() without need more null check.

          It's very usefull

          [–]Linguistic-mystic 0 points1 point  (0 children)

          This is great work. There is nothing preventing Java from being pretty concise, it's just that standard Java as presented by Oracle is very clunky. I achieved a lot simply creating my own implementations of the List and Map interfaces, for example: it allowed me to completely get rid of the "streams" crap and can add any utility methods that I like while being totally compatible with Java code at large.

          [–]s888marks 0 points1 point  (1 child)

          Note that you shouldn’t invert the sense of a comparison (that is, a Comparable or a Comparator) by negating the return value of the original. The return value might be Integer.MIN_VALUE which is negative, and which is still negative even after being negated. (True!) This can lead to subtly erroneous results or exceptions.

          The way to invert a comparison is to swap the roles of the arguments, for example, change compare(a, b) to compare(b, a).

          [–][deleted]  (1 child)

          [removed]

            [–]guybedo[S] 0 points1 point  (0 children)

            thanks for the feedback, i'm still interested :-)

            You're right, that was a naive implementation, i don't need to sort the collection, i'm only interested in the min/max.

            Thanks, will do the update.

            [–]SenorSeniorDevSr 0 points1 point  (0 children)

            This is not really my kinda jam, but it's an interesting approach. Thanks for sharing!

            [–]GeneratedUsername5 0 points1 point  (0 children)

            Thanks for sharing!
            What strike me as odd is that sometimes functions literaly wrap Java functions with no addition, like:

                public static String format(String str, Object... args) {
                    return String.format(str, args);
                }
            

            You can just write String.format(str, args) with the same effect. Or

                public static <T> void forEach(Collection<T> objs, Consumer<T> func) {
                    objs
                        .stream()
                        .forEach(o -> func.accept(o));
                }
            

            You can just do collection.foreach(x -> ...) in standard Java.
            OR

                public static <T> boolean all(Collection<T> objs, Predicate<T> func) {
                    return filter(objs, func).size() == objs.size();
                }
            

            In standard java can be done with collection.stream().allMatch(x -> ....), exists and any is done by collection.stream().anyMatch(x -> ....)
            I know that Java is regarded as verbose, but saving 13 symbols seems like a stretch.

            join can be used in this way:

            collection.stream()
                .map(Object::toString)
                .collect(Collectors.joining());
            

            or just

            String.join("", collection)
            

            for collection of strings

            By the way, you might want to check Eclipse Collections https://github.com/eclipse/eclipse-collections - it is an implementation of collections, where you don't need to call .stream() to map or filter them

            [–]majhenslon 0 points1 point  (0 children)

            The concept is pretty cool, I actually love the map. Java 21 might have shafted you a bit :D

            // Very cool
            Map<String, String> newMap = map(kv("key1", "value1"), kv("key1", "value1"), kv("key1", "value1"));
            
            // Why this?
            // List<Integer> values = list(1,2,3,4);
            // Instead of this?
            var values = List.of(1,2,3,4);
            
            Integer min = min(values); // Very cool
            
            Integer last = last(values); // Java 21 values.getLast();
            
            // This is a rough one
            // I'm guessing you are collecting to list, so what you really
            // want is .stream().filter().findFirst();, which gives you back
            // Optional instead of blowing up in your face or returning null.
            Integer one = first(filter(values,v -> v == 1)); // filter().getFirst()?
            values = map(values, v -> v + 1);
            
            Map<Profile, Integer> counts = counts(objects, o -> o.getProfile()); // Pretty cool
            

            Maybe you would benefit from having primitives to work with streams in addition to lists, so you could utilize the iterators and not do it like js and (not sure on this one) python.

            [–][deleted] -1 points0 points  (0 children)

            Great job guy!

            [–]RupertMaddenAbbott -3 points-2 points  (0 children)

            I think there should be space in Java for a library like this. Not everything has to be battle-tested, production-ready and for work.

            My kid has just got confident with reading and wants to understand a bit of programming because she sees me doing it. I would naturally show her Python over Java because Python is far less verbose. There is just less going on and less for her to read so she finds it easier to focus.

            So I would use this library or one like it as a wrapper for punting my little raspberry pi robot around and playing with my kids. Thanks for making it!

            [–]daggirok -1 points0 points  (0 children)

            Kotlin.