DEV Community

Dmitry Bogomolov
Dmitry Bogomolov

Posted on • Edited on

gosli: a little attempt to bring a bit of LINQ to Golang

This post was originally posted on Medium: https://medium.com/@dmitrybogomolov/gosli-a-little-attempt-to-bring-a-bit-of-linq-to-golang-1e3fb4acfd04

First, let me admit that I really like Golang. I think it’s pretty elegant, easy-to-read and very powerful language. But also, I love C#.

Though I know that every language has its’ best practices and has its’ own way to create good software, there’s something from C# that I definitely miss in Golang. One of those things is LINQ (Language-Integrated Query).

LINQ allows us to manipulate with collections in C#, modifying, filtering, grouping them using anonymous (lambda) functions. Let’s see a little example how it looks:

The problem

One of the biggest inconveniences I met in Go was the fact that the language doesn’t have some standard way to filter a slice or to find an element of slice by one of its’ fields. I tried to find some way to do it, but everything I found was something like “Well, you have to write a loop for it”.

Once upon a time, I found myself having multiple loops in my program, and they were almost the same but working with different types. It was something like that.

It made me think that it should be the way to avoid code copying. One approach is to make common method receiving interface{}.

And there are at least a couple of problems I see in this implementation.
First, a lot of type assertions are there (like x.(B).FieldString… etc.). It doesn’t look very nice and it makes us create a lot of code to check if assertion can be made.

Also, it makes us use the reflect package. A lot of Go apps are made to be fast, I mean, really fast. And you could hear it here and there that reflect is not a great helper if you’re looking for good performance.

And another disadvantage is the underlying type returned by our filter method. Let’s check it with fmt.Printf(“%T”, filteredA) and it will return us: []interface {}. If we want to use this object it wouldn’t be to cool to do. We will need to iterate over it to convert every element’s type to A.

So, all these problems made me think that we shouldn’t make a pseudo-generic method for all the types. But writing a separated loop for every type (as we did in the first code example) doesn’t look too good as well.

I supposed that making a code generator to create some general methods that won’t use the reflect package inside of those methods could make a deal. That’s how we can avoid copying-and-pasting code all the time and also we could have nice and clear methods that receives and returns particular types instead of annoying interface{}.

Introducing gosli

These thoughts lead me to make a lib I called gosli. I didn’t really plan to make a big deal out of it, I just wrote some code that, I guess, could be useful for someone else.

TL;DR: check the source code and examples of usage here
https://github.com/doctornick42/gosli

Ok, let’s start. To get it we need to run this command:
go get github.com/doctornick42/gosli

Let’s create a new project with such a structure:

Basic project structure

testtypes.go will contain our types from the previous examples:

After it, we should run this for Linux

$GOPATH/bin/gosli types/testtypes.go A

or this for Windows:

%GOPATH%\bin\gosli.exe types\testtypes.go A

As you can see, our types folder has been extended by adding 3 new files:
Generated files

The only file of them we need to modify manually is a_equal.go, which is needed for gosli to understand how it can check if two instances of A are equal. From the scratch the code of this file looks like:

Let’s append after the //equalmethod has to be implemented manually comment a new line:

return r.FieldInt == another.FieldInt

That’s it. Now we can make some code to test the result, let’s just use the code we used in a previous example and put it in our main.go file:

And if we run the program we will see this output: [{“FieldInt”:1},{“FieldInt”:3}] which is correct.

The full code of this example is here.

Features of gosli

The library has 2 main approaches:

  • to provide methods for slice of custom types. This part uses code generation, it has been briefly described in the previous paragraph. The documentation showing all the methods and other details could be found here;
  • gosli also has all these methods for basic Golang types out-of-the box, it’s described here.

Also, every method of -Slice types returns such a type too, so the methods could be combined in a chain like this:

result, err := SomeTypeSlice(original).
    Where(filter1).
    Where(filter2).
    Page(1, 2)
Enter fullscreen mode Exit fullscreen mode

So, thank you for reading. If you would like to check out gosli, feel free to ask me about it or create issues in the repository.

In the experiment folder there are some tests, benchmarks and examples, that I hope will help to understand how to use the library in your apps.

Also, you can find more awesome gopher images (like one I used in this article) by Egon Elbre under the CC0 license here https://github.com/egonelbre/gophers

Top comments (0)