If you like this blog or my talks, consider helping me acquire astronomy equipment

Goodbye, Groovy!

21 March 2019

Tags: gradle groovy kotlin

I’m stepping down from the Apache Groovy project

Today is a very sad day. I’ve decided to resign from the Apache Groovy PMC, as well as a committer.

The trigger was that I dared pushing a Gradle Kotlin DSL and this had to be reverted. Lots of people in the Groovy community see Kotlin as a threat. I don’t. I use both languages everyday, for different purposes. I wrote most of the static compiler for Groovy, and I have no problem saying that I believe Kotlin is better as a static language: the experience is more consistent, and very pleasant. But nothing in Kotlin reaches the simplicity that Groovy offers. Running a single line script, the awesome Spock framework for testing, …

The fact I work for Gradle Inc was also mentioned as a reason why I did push this file. To me, this is questioning my integrity, which is something I cannot accept.

In practice, since Gradle announced a couple of years back that they would start using Kotlin as a language for its DSL, I’ve been in a very complicated position. Privately, I always get pinged whenever I say something good about Kotlin. Like, you should not do this, this is not good for us, or, Gradle is not nice with Groovy, what are you doing. People always make jokes about Kotlin when they talk to me. I was at Gr8Conf Europe a few days after the announcement and man, that wasn’t a pleasant experience.

I am Cédric. I am not Gradle Inc.

I am Cédric. I am not Kotlin.

I am Cédric. I am not Groovy.

Technologies live and die, I’m not interested in being married with a technology, I’m interested in working with something that excites me. The reality is that I’ve been more excited with Kotlin than Groovy lately, and the attacks, conscious or not, public or private, that I get everytime I mention this language are tiring.

Now, when I’m going to say something good about Groovy, I hope folks will believe me.

Special thanks

I want to thank Guillaume Laforge for this adventure. Man, you gave me the opportunity to join the project, its awesome community. You gave me a job, I was lucky enough to be paid to work on open-source software, and that’s all because of you. For that I owe you everything, thank you!

Second, thanks to Paul King. You are the one that the Groovy community should never loose. People have mentioned that I would be a great loss, but that’s not true: the reality is that I contributed almost nothing recently, and you did all the hard work. You are also an amazing person always trying to get the best of us. I will miss it.

Then Jochen Theodorou: thanks to you for all the hard work you’ve done with the compiler. I would not have been able to write the static compiler without your help and insights on the internals of Groovy.

I’d also would like to personally thank folks that make the community vibrant: Søren Berg Glasius, Graeme Rocher, Jeff Scott Brown, Iván López, Álvaro Sánchez and all the others I miss, don’t take it personally, this is not easy for me.

Last but not least, thank you to the Groovy community. You’ve been awesome, and I wouldn’t be where I am without you. I may have been a bit sharp saying that the Groovy community as a whole sees me as a Trojan horse, that’s not true, but the truth is that I don’t want to fight anymore. Do not assume that because I quit the community is toxic: it’s not. There’s just a lot of understandable fear for the future, in my opinion. My advice is, embrace the change, don’t look back, and take inspiration.

I want to say good luck to the team, in particular to its newer members, like Daniel Sun, who are the future of this language: they have the energy, the skills to make it better.

Last but not least, I’m still a friend of Apache Groovy, you should be too!

Comments

A simple native HTTP server with GraalVM

19 March 2019

Tags: gradle graal groovy kotlin

Writing a simple HTTP server with GraalVM

In my daily work, I often need to start a simple HTTP server to serve local files. For example, this week I’m going to give a talk at Breizcamp, and because my presentation uses a Reveal.js slide deck and that it loads resources dynamically, I need a "real" web server to serve the files. So far, I’ve been quite happy using the Python simple http server. Using it is as easy as running:

python -m SimpleHTTPServer 8000

But knowing that the JDK has an embedded HTTP server, and that there’s a lot of hype around Graal those days, I wanted to see if we could achieve the same thing, with a fast startup, with GraalVM. The answer is yes, but the road wasn’t so easy, at least for Groovy.

Show me the code

The code for this experiment can be found on GitHub. We’re going to use:

And because I like the Groovy, and especially its static compiler, my first attempt was to use statically compiled Groovy to do this. Well, it turned out to become a nightmare, so after an hour trying to make it work, I switched to Kotlin, and try to make it work there first. Knowing that Kotlin is a statically compiled language from the ground up and that it doesn’t have the whole dynamic history of Groovy, I did expect it to be simpler.

So, in the end, here’s what the Kotlin server looks like:

HttpServer.kt
fun main(args: Array<String>) {
    val port = if (args.size > 0) args[0].toInt() else 8080
    val baseDir = if (args.size > 1) File(args[1]).canonicalFile else File(".").canonicalFile

    create(InetSocketAddress(port), 0).run {
        createContext("/") { exchange ->
            exchange.run {
                val file = File(baseDir, requestURI.path).canonicalFile
                if (!file.path.startsWith(baseDir.path)) {
                    sendResponse(403, "403 (Forbidden)\n")
                } else if (file.isDirectory) {
                    val base = if (file == baseDir) "" else requestURI.path
                    sendResponse(200, "<html><body>" +
                            file.list()
                                .map { "<ul><a href=\"$base/${it}\">${it}</a></ul>" }
                                .joinToString("\n") + "</body></html>")

                } else if (!file.isFile) {
                    sendResponse(404, "404 (Not Found)\n")
                } else {
                    sendResponse(200) {
                        FileInputStream(file).use {
                            it.copyTo(this)
                        }
                    }
                }
            }
        }
        executor = null
        println("Listening at http://localhost:$port/")
        start()
    }
}

It’s quite simple indeed, and making this work as a GraalVM native image is extremely easy too. This is the whole build file, this is all you need:

build.gradle.kts
plugins {
   kotlin("jvm") version "1.3.21"
   id("com.palantir.graal") version "0.3.0-6-g0b828af"
}

repositories {
   jcenter()
}

dependencies {
   implementation(kotlin("stdlib"))
}

graal {
   graalVersion("1.0.0-rc14")
   mainClass("HttpServerKt")
   outputName("httpserv-kt")
   option("--enable-http")
}

As you can see, we just apply the Kotlin plugin to build our code, then the GraalVM plugin and configure the basics of the GraalVM plugin (version, main class, …).

Building the image can be done by calling:

./gradlew http-kotlin:nativeImage

As you can see, building the whole thing takes around 15s on my laptop. That is to say, compiling the server and generating the native image. Then you can try to serve files running:

http-kotlin/build/graal/httpserv-kt 9090 /path/to/files

You’ll see that the server starts immediately: there’s absolutely no wait time, it’s there and ready to answer. The whole process took me less than 30 minutes, the native image is only 11MB. Success!

Making it work with Groovy

Now that I had a proof-of-concept with Kotlin, I went back to Groovy. And, I can say, despite the fact I love this language, that it was a nightmare to make it work. At some point, I even thought of abandoning, however, using perseverance, I managed to work around all problems I faced.

Before I explain the problems, let’s took a look at the final Groovy server:

HttpServerGroovy.groovy
@CompileStatic
abstract class HttpServerGroovy {

    // VERY dirty trick to avoid the creation of a groovy.lang.Reference
    static File baseDir

    static void main(String[] args) {
        def port = args.length > 0 ? args[0].toInteger() : 8080
        baseDir = args.length > 1 ? new File(args[1]).canonicalFile : new File(".").canonicalFile

        def server = HttpServer.create(new InetSocketAddress(port), 0)
        server.createContext("/", new HttpHandler() {
            @Override
            void handle(HttpExchange exchange) throws IOException {
                def uri = exchange.requestURI
                def file = new File(baseDir, uri.path).canonicalFile
                if (!file.path.startsWith(baseDir.path)) {
                    sendResponse(exchange, 403, "403 (Forbidden)\n")
                } else if (file.directory) {
                    String base = file == baseDir ? '': uri.path
                    String listing = linkify(base, file.list()).join("\n")
                    sendResponse(exchange, 200, String.format("<html><body>%s</body></html>", listing))

                } else if (!file.file) {
                    sendResponse(exchange, 404, "404 (Not Found)\n")
                } else {
                    sendResponse(exchange, 200, new FileInputStream(file))
                }
            }
        })
        server.executor = null
        System.out.println(String.format("Listening at http://localhost:%s/", port))
        server.start()
    }

    private static List<String> linkify(String base, String[] files) {
        def out = new ArrayList<String>(files.length)
        for (int i = 0; i < files.length; i++) {
            String file = files[i]
            out << String.format("<ul><a href=\"%s/%s\">%s</a></ul>", base, file, file)
        }
        out
    }
    ...

The first thing you will notice is that it’s far from being idiomatic Groovy. Of course I used @CompileStatic, because the static nature of GraalVM would have made this an even greater challenge to make it work with dynamic Groovy. However, I didn’t expect that it would be so hard to make it work. The resulting file is both a consequence of limitations of GraalVM, and historical background of Groovy.

Where are my closures?

The first code I wrote was using idiomatic Groovy, with closures. However, as soon as I started to build my native image, I noticed this obscure error:

com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Invoke with MethodHandle argument could not be reduced to at most a single call: java.lang.invoke.MutableCallSite.<init>(MethodHandle)

It’s funny to see this MethodHandle error when you know that the code is fully statically compiled, and that it doesn’t contain a single method handle. However, the Groovy runtime does, and this is where the fun began. First of all, GraalVM tells you what method is problematic. This was org.codehaus.groovy.vmplugin.v7.IndyInterface.invalidateSwitchPoints. Things are getting a little clearer: for some reason, the Groovy runtime is initialized, and we load the dynamic IndyInterface, that I won’t ever need.

The "some reason" needs a bit of explanation. Despite the fact that we use statically compiled Groovy, we’re still implementing Groovy specific interfaces. For example, the GroovyObject interface. Similarly, we honor class initialization the same way as a dynamic class, meaning that when a statically compiled Groovy class is instantiated, even if it doesn’t contain any dynamic reference, we will initialize its metaclass, and as a consequence try to initialize the Groovy runtime.

However something was wrong: looking at my code I could not figure out what would cause initialization, because my entry point was static. In fact, the answer was easy: it came through the closures.

Well, that’s what I thought, because even after eliminating closures, I still got the damn error. In fact, it turns out the situation is far more complex. For example, I had this innocent looking code:

def baseDir = args[0]
server.createContext("/", new HttpHandler() {
    @Override
    void handle(HttpExchange exchange) throws IOException {
        ...
        someCodeUses(baseDir)
    })

The fact that we use baseDir within an anonymous inner class, and that Groovy uses the same code generation under the hood for both closures and anonymous inner classes, that the baseDir variable is allowed to be mutated in the inner class. Of course here I’m not doing it, but because the compiler doesn’t eliminate that possibility, what it does is generating a groovy.lang.Reference for my local variable, that is used in the inner class. And, initializing the Reference class would cause an additional path to this IndyInterface method call…

In the end, the problem is not that much that there’s a MethodHandle, it’s that there are potentially different code paths that lead to this, and that GraalVM can’t figure out in the end a single method to be called: we’re just defeating the system!

For example, even creating an anonymous inner class would still trigger the creation of a metaclass for it: this means that even if we replace the closure with an inner class, in the end, we would still trigger the initialization of the Groovy runtime.

I tried to be smart and remove the IndyInterface from the code that GraalVM is using to generate the native image, knowing that in the end, this code would never be called if I didn’t register the Java 7 plugin (that I wouldn’t use in any case). However, it turns out that GraalVM doesn’t like this, as it has special handling for Groovy, and that if you remove that class, it fails with:

Error: substitution target for com.oracle.svm.polyglot.groovy.Target_org_codehaus_groovy_vmplugin_v7_IndyInterface_invalidateSwitchPoints is not loaded. Use field `onlyWith` in the `TargetClass` annotation to make substitution only active when needed.

So instead I spent hours eliminating those paths, which involved:

  • turning that shared variable into a field in order to workaround the reference initialization

  • removing all closures

  • removing usages of GString (interpolated strings, which is why you see String.format instead)

  • replacing the short-hand syntax for creating lists (def foo=[]) with an explicit call

  • removing calls to + with strings (first attempt to remove GString…)

  • eliminating some classes from the Groovy runtime

  • replacing some classes of the Groovy runtime with stubs, preventing static initialization

In the end, I have something which works, but you can see that the build file is far more complex.

In particular, it makes use of a little known Gradle feature called artifact transforms. Basically, I’m asking Gradle to transform the Groovy jar before GraalVM uses it. This transformation involves filtering out classes, so that GraalVM doesn’t try to be too smart about them.

Once this is done, we can finally generate a native image for Groovy too:

./gradlew http-groovy:nativeImage

It takes about the same amount of time as with Kotlin to generate a similar 11MB native image. Running it is as easy:

http-groovy/build/graal/httpserv-groovy 9090 /path/to/files

And again it’s super snappy!

Conclusion

At this stage, you might consider that it’s a success: we got both Kotlin and Groovy code compiled into a native image that is very snappy and starts even faster than the Python server. However, getting the Groovy version to work was hours of pain. Each time I managed to fix a problem, another one arose. Basically, every method call, every extension method you call is likely to trigger initialization of some Groovy subsystem, or trigger additional paths to this IndyInterface code. In the end it would be nice if GraalVM could completely eliminate the need for having this class, but until then I just cannot recommend anyone to use Groovy to build native images: it’s just too frustrating. And remember that even if you manage to make it work, it takes both a significant amount of time to do so, but also forces you to write non idiomatic code. Last but not least, any addition to your code is likely to force you to update your GraalVM configuration to make it work. In the end, it’s just way easier to write plain old Java code, or go Kotlin.

Note that I’m not saying that it’s not possible with Groovy, but folks usually face different problems than I did, in particular when it’s just about configuring classes accessed by reflection: this is a simple problem. I’m not saying either that you should avoid Groovy: I just think it’s not suited for this use case. I still use Groovy everyday, in particular in tests or for simple scripts (in replacement to bash scripts). However, more worrisome is that if an application transitively depends on Groovy, it’s unlikely to be "GraalVM compatible".

Eventually, if you look at the Kotlin version and the companion Gradle build, it’s extremely simple, thanks to the great work done by the Palantir team!

Note
After this blog post was published, I received a pull request from Szymon Stepniak improving the Groovy code a lot. The resulting file is, however, twice as big (23MB!). It does not change my vision on this either, because it took 2 men to reach this point, in a significant amount of time.

Comments

Gradle myth busting: lifecycle

11 September 2018

Tags: gradle maven lifecycle

The build lifecycle

There’s a very common misconception I read from Maven users about Gradle: that there’s no default lifecycle. Not only this is wrong, but actually the Gradle lifecycle is significanly richer.

First, let’s explain in a few words what the lifecycle is in Maven. Detailed explanations can be found here, but in a nutshell, the idea is that _any build will always consist in a sequence of phases, and that each phase is built on top of the previous one. This has the advantage of being simple to explain: to deploy an application, you first need compile it, run the tests, package it, perform validation (checkstyle, …) and deploy. Let me ignore install which is an artifact of how Maven works.

Maven plugins attach themselves to those lifecycle phases, and define goals on different phases. For example, a code generator would attach itself to the generate-sources phase, and define a goal that runs at this phase. Fun begins when you have dependencies between the goals, and ordering matters… Anyway, the general idea is that if you want to get the outcome of, say, packaging, you have to execute all previous phases and consequently all goals that are defined on those phases. The other consequence is that the Maven lifecycle is biased towards the Java model, and more specifically, building Java libraries. It’s even clearer when you think about the term deploy, which doesn’t mean "deploy this application on production", but "push this jar on the external Maven repository". Similarly "install" doesn’t mean "install this application on my laptop", but "copy this library into my local .m2 repository". This, I would argue, is rather counter-intuitive…

Gradle, on the other hand, is a generic build tool. It’s aimed at the Java ecosystem, but also the native one, the Android ecosystem, Python, Go, … It doesn’t matter. All of those ecosystems have an underlying model, and the way you build applications or libraries in each of those ecosystems is different. Gradle offers the APIs to model the builds of each ecosystem. This flexibility is often what troubles Maven users, and makes them think there’s no lifecycle, but it’s not true.

Goals vs tasks

To understand why, we need to explain that Gradle model is not phase based, but task based. It’s a bit like what Ant did, but the similarity stops there. While Ant didn’t define any convention, any lifecycle, and everything was different from one build to the other, this is not true with Gradle. By default, if you apply this "Java library" plugin, you’ll get all the conventions you find with Maven:

plugins {
   id 'java-library'
}

This is the minimal build file you need to build, test and package a Java library with Gradle, with the same conventions as Maven src/main/java, … By applying this plugin, Gradle internally applies a sequence of plugins, which, in turn, would define new tasks, and more specifically for the topic of this blog post, lifecycle tasks.

In Gradle, a task is responsible for executing a unit of work. For example, "compile this source set". A task has inputs (the source files) and outputs (the class files). But a task also has dependencies. In particular, dependencies on other tasks. Gradle makes sure that a task graph is a DAG (direct, acyclic graph). This means that if you run compile on the command-line, what Gradle does is:

  1. compute the task dependencies of compile

  2. execute the tasks in order

Task dependencies can be explicit (say, compile.dependsOn(compileJava)), or implicit (because you add a source set as an input, and that this source set is generated by another task, then we know we need to run the code generation for this source set before). This model is nice because it’s significantly more fine grained than the phase one. When you execute a task, Gradle will always perform the minimal amount of work required to get the output of this task. Let’s illustrate with an example: say you want to run the unit tests of your library. You would run the test task with Gradle. Gradle would then determine that:

  • it needs to compile the sources of the library (compileJava)

  • but the sources includes a generated source set, so it needs to execute it (generateSources)

  • it would also find that the "resources" are an input of the test classpath, so execute the processResources task

  • etc…

But, in the end, it would not generate the jar file. Because Gradle knows that to run the tests, there’s no need to get the jar: we can build a classpath that consists of the generated classes and the resources directories. It’s actually very easy to figure out what the task dependencies are by running with --dry-run, or using a build scan.

So, will you tell me what the lifecycle of Gradle is then?

This is the trick. With Gradle, everything boils down to tasks, which are a bit like functions, with inputs and outputs. But there are special kinds of tasks, that we call "lifecycle tasks", which are binding other tasks together. They, effectively, produce no output individually. Their only role is to have dependencies on other tasks, so that we have nice shortcuts to produce our outputs. For example, the check task is a lifecycle task which has dependencies on the test task, but also the checkstyle task, etc… Plugins are free to add dependencies to the check task, and enrich the check lifecycle this way. But even better, by defining dependencies between tasks like that, and clearly defining the inputs and outputs of each task, we make it possible to get correct incremental builds, as well as caching (and no, this has nothing to do with ~/.m2).

The good news is that because lifecycle tasks are just regular tasks, it means they can also depend on each other, and you can build your own lifecycle tasks. It becomes very easy to model your build production pipeline. So here is a simple correspondance matrix for Maven users, for the Java library plugin:

Table 1. Lifecycle correspondance matrix
Maven Gradle Description

clean

clean

Removes the outputs of tasks

compile

classes

Generates the classes from source files

test

test

Executes unit tests

package

assemble

Creates a jar

verify

check

Runs all tests, integration tests, quality checks, …

install

publishToMavenLocal

Gradle doesn’t need a local repository, but should you need Maven interoperability, you can add the maven-publish plugin to add this task

deploy

publishToMavenRepository

This tasks is not available by default, as it depends on which type of repository you deploy to. In general you just apply the maven-publish plugin to add this task

But remember: in Gradle, tasks depend on each other. So it means that if you run a lifecycle task, only the tasks required for that specific target are going to be executed. Nothing more.

See us at Devoxx Belgium!

If you want to discover more of the differences between Gradle and Maven, come see my colleague Louis Jacomet and I during Devoxx Belgium, we’re giving a deep dive into Gradle where we’re going to cover what is explained here, and much more!

Comments

Assistants vocaux et robotisation: mon point de vue

03 July 2018

Tags: google home alexa robotisation

Ce billet essaie de résumer une conversation que j’ai eu avec mon frère, concernant les craintes autour des assistants vocaux, et, plus généralement, la robotisation. Je partage une grande partie de son point de vue, mais j’ai aussi de larges différences que je voulais résumer ici. C’est parti !

"Ok Google, crée un malaise"

Le week-end dernier, à l’occasion d’un barbecue en famille à la maison, ma moitié a, au détour d’une conversation, lancé cette phrase par pur réflexe:

"Ok Google, <question anodine>"

Là, mon frère, Guillaume, a fait un malaise. Etendu sur le sol, pris de spasmes incontrôlables, en position foetale, il baffouillait des phrases pour la plupart inintelligibles, mais, par moments, j’arrivais à comprendre des bribes comme "pas eux", "GDPR", ou autres "Saint Qwant venez-vous en aide". Après quelques minutes, l’intervention d’un seau d’eau et rongé par la honte de se montrer ainsi devant junior, Guillaume repris ses esprits, et commença une discussion trop courte parce qu’interrompue par la nécessité autrement plus primaire de faire griller quelques saucisses.

Au final, la question se résume à celle-ci: pourquoi ? De mon côté, je suis assez ouvert sur l’utilisation des assistants, et je dois dire que c’est quelque chose que j’attendais véritablement depuis des années. De lonnngggues années. Guillaume n’avait probablement rien noté jusqu’ici parce que mon assistant prenait la forme d’une enceinte bluetooth classique, alors qu’il s’agit en fait une enceinte Sony intégrant la technologie de Google. J’avais choisi ce modèle principalement parce qu’une des utilisations que je fais de cet assistant, probablement la plus fréquente, est de lui demander de me diffuser de la musique (via Deezer), ou la radio. La domotique, c’est un peu un rêve de gosse pour moi, j’adore tout piloter, par une appli, ou par la voix. Donc parmi les autres utilisations que j’en fais, il y a:

  • contrôler mes lumières, mon thermostat

  • contrôler ma box domotique (et donc les appareils non directement "connectés")

  • demander des informations (météo, à quelle heure est le match de la France, …)

  • enclencher un minuteur pour la cuisine, ajouter des rappels

Le problème, qui choque Guillaume, est que pour avoir accès à toutes ces fonctions, je partage énormément de données avec El Diablo (Google). Oui, mais j’en suis conscient. Et, par ailleurs, mon compte est configuré avec une extrême précaution. Notamment, j’ai complètement désactivé la personnalisation des annonces. Je garde néanmoins certaines fonctionnalités comme le traçage de ma position géographique (qui, par une occasion, m’a permis de démontrer à des amis qu’on était bien allé les voir au mois d’Octobre :)). J’ai plus de mal, en revanche, sur l’historique des recherches. Mais le noeud du problème, c’est qui détient les données, ce qu’ils en font et en particulier avec qui ils les partagent. Pour ma part, la seule raison pour laquelle j’utilise les services de Google est qu’ils me permettent techniquement de faire ce que je veux. Si, demain, j’ai la possibilité d’avoir une box locale qui m’offre les mêmes capacités, y compris lorsqu’invoquée via IFTTT, je signe à quatre mains. En attendant que celà existe, et j’espère sincèrement que celà arrivera au plus vite, je fais des compromis, et il faut être conscient de ce que l’on a a la maison.

Là où je suis moins d’accord, c’est la diabolisation à l’extrême des assistants. Je reviendrais plus tard sur quelques raisons, mais en particulier un argument que j’entends souvent, c’est "oui mais là tu as un micro qui écoute tout le temps chez toi et qui envoie tes données à Google". Ah, nous y voilà. Quel est le problème ?

  1. ça écoute tout le temps ?

  2. ça envoie tes données ?

  3. ça les envoie à Google ?

  4. qui traite des infos ?

Intention malicieuse ou non ?

J’avoue que j’ai du mal à comprendre les réticences ici. Probablement parce que je suis un développeur naif, mais les bugs, les amis, je suis désolé de vous l’apprendre, il y en aura toujours. Donc, oui, il arrivera que votre Google Home, votre Amazon Alexa ou autre, se mette à enregistrer une conversation qu’il n’aurait pas du, et l’envoie sur les serveurs de Google. En règle générale, on le comprend assez vite, parce que la douce voix robotique vous répondra quelque chose du genre "désolé, je ne sais pas comment vous aider". Que s’est-il vraiment passé ? La box a cru entendre un mot clé. Souvent, on se demande parfois pourquoi Google n’offre pas la possibilité de changer ce mot clé ("Ok Google", "hey Google", …). Je pense qu’une des raisons est ce que l’interprétation de ce mot clé est forcément locale. La puissance nécessaire pour reconnaitre parfaitement l’expression, et son contexte de déclenchement avec le bruit, est restreinte. Donc, parfois, pas de bol, ça croit entendre "Ok Google", ou, pire, ça s’enclenche lorsque la pub passe à la télé. En règle générale, ça fait plutôt rire mes enfants, rien de dramatique. D’autant qu’on sait que ça s’enclenche grâce au signal sonore "attention, Google écoute". Donc, un bug, c’est un bug. Ca n’est pas parce que ça peut envoyer des données sans votre accord que l’intention derrière est malicieuse. Dois-je vous rappeler les histoires d’activation à distance des webcams des Mac Book par la NSA ? On n’est pas dans la même catégorie, là. Enfin, des micros qui écoutent tout le temps, vous en avez en poche depuis des années: vos téléphones mobiles. Ils font exactement la même chose, sont activables à distance, et probablement infectés de tonnes de malwares, parfois installées en usine, à des fins d’espionnage gouvernemental. Si je dois me méfier de quelque chose, personnelement, c’est plus de ça que de Google recevant ma discussion sur tata Simone.

Par ailleurs, une fois la conversation envoyée sur le "cloud" (ça fait peur, le cloud, on ne sait pas ce qui s’y passe), elle est analysée. Pensez-vous que ce sont des humains qui analysent votre question et renvoie la réponse en 2s ? Non, la puissance de calcul nécessaire pour interpréter correctement votre question et titanesque. Elle nécessite des techniques avancées (TALN, statistiques, oui, vous savez, la même chose que ce gros mot "big data"), et l’apprentissage pour l’amélioration de la qualité des réponses requiert le stockage de quantités monstrueuses d’information. Je ne suis pas convaincu que Google doive pour autant stocker toutes les conversations, mais je conçois parfaitement qu’en analysant les requêtes de millions d’utilisateurs, avec des voix différentes, des accents différents et des contextes culturels différents, on est capable de faire beaucoup mieux qu’en ne stockant rien et analysant localement. Le temps où il fallait patiemment passer des heures à répéter des tonnes de phrases à son logiciel de reconnaissance vocal n’est pas si loin…

Donc, oui, Google va recevoir ma voix. Oui, il va l’analyser et construire un profil, qu’il pourra partager. Je suis au courant. Et j’ai, dans la mesure du possible, restreint ces possibilités. Je serai d’autant plus heureux le jour où je n’aurai pas besoin de lui confier mes données. Mais l’intégration, la simpliciter d’utilisation, et le seul fait que ça marche, aujourd’hui, est la raison de son adoption. Enfin, on dit que ça marche, mais après des mois d’utilisation, il ne faut pas très longtemps pour comprendre que malgré les quantités énormes de données dont Google dispose, dans de très nombreux cas, l’assistant est complètement à la ramasse. Genre, on lui demande si un match passe sur TF1, il est incapable de répondre. On lui demande "quel temps va-t-il faire", il comprend "température". On apprend même à reformuler nos questions pour que l’assistant les comprenne. Bizarre, et frustrant. Mais, ça montre les limite de la technologie, et casse quelques mythes. En particulier, il faut se méfier des annonces autour de Google Duplex, l’assistant qui prend des rendez-vous à votre place: oui, ça marche, c’est bluffant, mais il est conçu pour cette tâche spécifique, et il est finalement assez simple, d’après les premiers retours, de lui faire perdre le fil.

La relation homme-machine

Néanmoins, la conversation a dévié vers l’intelligence artificielle en règle générale, et la relation qu’on a avec les machines. Sur ce sujet, je suis extrêmement ouvert. Je suis très introverti, et, personnellement, avoir une conversation avec une machine a un côté rassurant. Me demandez pas pourquoi, c’est un fait. Je narrais aussi cette anecdote à mon frère: un de mes enfants a des difficultés à parler aux adultes. Il marmonne, ne les regarde pas dans les yeux, et met beaucoup de temps à leur accorder sa confiance (mais une fois qu’elle est acquise, il n’y a plus de pb). Personnellement, ça ne m’a jamais inquiété, j'étais pareil enfant, et ça n’a (je pense) pas fait de moi un être associal (j’espère). Disposant de Google Home, mon fils s’est naturellement mis à l’utiliser lui aussi. Et ses premières expériences étaient frustrantes: n’articulant pas, la machine n'était pas capable de le comprendre. En quelques semaines seulement, il a progressé plus qu’en plusieurs années: désormais, il articule et parle fort, y compris aux adultes (à de rares exceptions près pour ceux qui l’impressionnent comme le médecin). Ce sujet en a apporté un autre: celui du fait de faire dire aux enfants "merci" ou "s’il te plait" aux machines.

Guillaume est, si j’ai bien compris, contre l’humanisation des machines. Pour ma part, j’y suis favorable, et je suis donc pour la "politesse envers les machines". Fondamentalement, je pense qu’une partie des craintes de Guillaume se fondent sur le fait de confier l’empathie aux machines et que des algorithmes s’en servent pour nous manipuler. L’autre aspect est l’aliénation de l’homme à la machine, c’est à dire apprendre à des enfants à obéir non plus à des hommes mais à des machines. Soit, c’est une crainte légitime. Je suis plus cynique que celà: je doute qu’une machine puisse faire pire qu’un être humain. En fait, j’ai plus d’espoir envers les machines que je n’en ai envers les êtres humains. L’histoire a montré a de trop nombreuses occasions que l’empathie n’est pas l’apparat de tout le monde, bien au contraire. L’utilisation des biais cognitifs à des fins de manipulation (vente, politique, abus de confiance), ça n’a rien de nouveau. Est-ce qu’il faut craindre que les algorithmes fassent de même ? Oui. Serait-ce pire qu’avec des humains ? J’en doute, bien au contraire. L’imagination perverse des hommes n’est plus à démontrer. Leur intolérance non plus.

Suis-je humain ?

J’ai grandi avec Star Trek.

Cette série est probablement celle qui a influencé une grande partie de ma pensée. Déja dans Star Trek TOS, l’ordinateur et la domotique étaient au coeur de la relation homme-machine. La machine était naturellement intégrée, un personnage "vivant" en quelque sorte. Puis, arriva Star Trek The Next Generation, avec des personnages encore plus humanistes, mais un personnage en particulier, Data, change la donne: un Androide dont la seule quête était de devenir humain. Un membre à part entière de l'équipage. Un épisode en particulier traite spécifiquement du sujet du droit des robots: Data devait-il être considéré comme une machine ou un humain ? Un scientifique a-t-il le droit de le démembrer pour l'étudier sur le seul prétexte qu’il s’agit d’une machine ?

J’adore cette série pour celà: son humanisme, sa capacité à rechercher ce qu’il y a de meilleur chez l’homme, et son ouverture en général. Subtilement mais sûrement, les sujets sensibles sur le racisme, le transhumanisme, l’homosexualité y sont traités.

Je fais donc partie de ceux qui préfèrent avoir une "conversation" avec une machine polie, lui répondre "merci", tout en sachant qu’il ne s’agit que d’une machine, plutôt de de causer à un con… raciste, homophobe, ou plus fréquemment un télécommercial essayant toutes les techniques possibles pour me vendre ses panneaux solaires sachant pertinnement que je veux terminer ma conversation avec lui. Je pense que la politesse, le "savoir vivre", procure une certaine satisfaction, une relaxation, qui est un concept totallement orthogonal avec le sujet (l’homme ou la machine). Il n’est pas surprenant pour moi que des autistes s’ouvrent plus facilement avec la présence d’un animal (chien, chat, peu importe), ou que certaines expériences montrent des enfants faisant des progrès en parlant à des robots. L’interaction, la socialisation est importante. Et si le robot est meilleur que l’homme sur ce sujet, on doit s’en réjouir ! L’homme a toujours cherché à créer des choses à son image (ça ne vous rapelle rien ?) et il se trouve que notre cerveau est conçu pour reconnaitre la douleur de l’autre (empathie) et y réagir. Je préfère donc 100 fois, que dis-je, 1000 fois un homme poli envers un robot, qu’un homme traitant une autre personne comme une machine. C’est peut-être naif, mais j’ai espoir qu’en apprenant à des enfants à être polis envers des machines, ils apprendront d’autant plus à l'être envers les autres, et qu’on effacera une partie de ce mal, qui est la disparition à l'échelle de la société de l’empathie. Et mon rêve de gosse, c’est de voir un Data émerger. Montrer qu’avec suffisamment de puissance, une machine puisse prendre son autonomie. On en est encore loin, mais, on réduit déja ce qui est "visible" entre homme et machine. Si je ne suis pas capable de faire la différence entre un homme et une machine dans une conversation, qu’est-ce que celà dit de moi ? Qu’est-ce que celà dit de la machine ? Et si, nous mêmes, n'étions finalement qu’une machine un peu sophistiquée, issue de l'évolution. Une machine douée de pensée et de sentiments, mais une machine quand même.

Comments


Older posts are available in the archive.