Fork me on GitHub
#beginners
<
2020-11-15
>
Kazuki Yokoyama01:11:24

Is there a way to create a macro (let's call it m) that works like that: (m (f 1) (f 2)) expands to ((future (f 1)) (future (f 2))) , ie a seq of future calls (note that (f 1) and (f 2) were not evaluated yet)?

Kazuki Yokoyama01:11:45

The problem with, say,

(defmacro m
  [& fns]
  `(map #(future %) ~fns))
Is that it expands to something like
(clojure.core/map (fn* [p1__11890__11891__auto__] (clojure.core/future p1__11890__11891__auto__)) ((f 1) (f 2)))
and then (f 1) and (f 2) get evaluated before the map takes place

jsn01:11:12

`(defmacro m [&amp; args] `(list ~@(for [a# args] `(future ~a#))))` ?

👍 3
Kazuki Yokoyama02:11:33

It works, thanks.

souenzzo02:11:39

Without macros

(map (fn [[f & args]] (future (apply f args)))  [[prn 1] [prn 2]])

hiredman02:11:04

user=> (doc pvalues)
-------------------------
clojure.core/pvalues
([& exprs])
Macro
  Returns a lazy sequence of the values of the exprs, which are
  evaluated in parallel
nil
user=>

hiredman02:11:30

(uses pmap, so gross)

hiredman02:11:50

in general laziness + futures = weirdness

Kazuki Yokoyama02:11:53

Thank you all for the answers! pvalues is exactly what I was trying to do in the end. Well, looking at the source code for pvalues, and then pcalls, and finally pmap, I think it was not a trivial task at all.

Ben Sless10:11:20

Just beware if your functions are side-effectful. Laziness + futures + side effects = sadness

Tony Vo17:11:30

Hi, I ran into this issues and couldn't figure it out

Tony Vo17:11:02

here's the prod code:

(defn get-neighbors [[x y]]
	#{[(dec x) (inc y)] [x (inc y)] [(inc x) (inc y)]
		[(dec x) y] [(inc x) y]
		[(dec x) (dec y)] [x (dec y)] [(inc x) (dec y)]})

(defn get-all-neighbors [world]
	(mapcat #(get-neighbors %) world))

Tony Vo17:11:30

and here's my test code:

(context "get-all-neighbors"
  (it "should return all neighbors"
    (should= ([1 0] [-1 0] [1 1] [-1 -1] [1 -1] [-1 1] [0 -1] [0 1] [0 0] [1 0] [-1 0] [1 1] [-1 2] [0 2] [-1 1] [1 2]) (get-all-neighbors [world-of-two])))
  )

Tony Vo17:11:53

with the world-of-two define as:

(def world-of-two #{[0 0] [0 1]})

Tony Vo17:11:32

and herew's the error I get: 1) Game of life get-all-neighbors should return all neighbors nth not supported on this type: PersistentHashSet java.lang.UnsupportedOperationException: nth not supported on this type: PersistentHashSet ... 2 stack levels elided ... at game_of_life$get_neighbors.invokeStatic(game_of_life.clj:4) at game_of_life$get_neighbors.invoke(game_of_life.clj:4) at game_of_life$get_all_neighbors$fn__10467.invoke(game_of_life.clj:10) ... 9 stack levels elided ... at game_of_life$get_all_neighbors.invokeStatic(game_of_life.clj:10) at game_of_life$get_all_neighbors.invoke(game_of_life.clj:9)

herald17:11:38

Should (get-all-neighbors [world-of-two]) perhaps be (get-all-neighbors world-of-two)? It seems to fail at destructuring.

Tony Vo17:11:20

damn .. you're right

Tony Vo17:11:26

this is the second time I ran into this issue

Tony Vo17:11:45

keep forgetting I don't need to use [] on method invocation

Tony Vo17:11:53

BAD HABIT!!!!!

herald17:11:01

haha, I'm curious where that habit comes from?

Tony Vo17:11:44

actually it still fail after removing that []

herald17:11:48

I ran your code without the [] and it seems to work for me: ([1 0] [-1 0] [1 1] [-1 -1] [1 -1] [-1 1] [0 -1] [0 1] [0 0] [1 0] [-1 0] [1 1] [-1 2] [0 2] [-1 1] [1 2])

Tony Vo17:11:16

yeah ... from repl .. that's what I print out for me

Tony Vo17:11:26

but from lein spec, it failed

herald17:11:56

can you try quoting the list after should=?

herald17:11:11

so it becomes (should= '(

Tony Vo17:11:28

damn .. quote works

herald17:11:51

No problem! Remember that lists will get evaluated if you don't quote them.

Tony Vo17:11:07

what do list evaluated to?

herald17:11:11

Like a function

Tony Vo17:11:31

so what does this evaluate to ? -> ([1 0] [-1 0] [1 1] [-1 -1] [1 -1] [-1 1] [0 -1] [0 1] [0 0] [1 0] [-1 0] [1 1] [-1 2] [0 2] [-1 1] [1 2])

herald17:11:34

So ([1 0] [-1 0] ... will basically try to call the first vector as a function, with everything else as arguments.

Tony Vo17:11:37

without the quote?

herald17:11:59

yes. I get Wrong number of args (15)

herald17:11:25

but if you had ([1 0] 0) it would return 1, since you can use vectors as functions with the index to get as an argument.

herald17:11:03

Also, lists and vectors are treated as equal in Clojure (= [] '()), so it's more common to use vector literals in tests like that

Tony Vo17:11:46

thanks ... this is deep

herald17:11:01

hehe, it will come naturally soon enough [=

Tony Vo18:11:06

thanks, it's my fervent hope and dream

👍 3
Santiago19:11:22

How would you conj vals in equal keys in different maps to create a single map?

(def a {:foo [123] :bar [345] :buzz [45]})
(def b {:foo [888] :bar [777]})

; result should be {:foo [123 888] :bar [345 777] :buzz [45]}
I feel there’s some combo of reduce and map involved here?

seancorfield19:11:06

merge-with into might do what you need -- depending on what other types of values you have in the map.

seancorfield19:11:45

user=> (merge-with into a b)
{:foo [123 888], :bar [345 777], :buzz [45]}
user=>
@slack.jcpsantiago

Santiago19:11:22

ah I knew there was an elegant solution for this instead of monkeying around 😄 yeah that’s what I was looking for thanks!

seancorfield19:11:58

Note: if you have values in the map that are not vectors, merge-with into will fail -- because into can only merge vectors.

Thomas Tay20:11:52

Is there a corelib function that is the equivalent of F#s Option.map? Basically, I want to do nil punning for my functions. This function would take as input my function F, and return a function that, if has input nil, returns nil. If input is non nil, pass it to my function F Is there such a wrapper function? Or is this not something that clojurians do? I come from a F# background so this is how I was approaching this problem. Background: I have a function that takes in a string and does some processing. I want to map this function over a Map (for a given set of keys), but it is possible the Map doesn't have the key, so it might return nil. In this case, I want to punt the nil down the line, so to speak.

seancorfield20:11:33

Or (map #(and % (f %)) data) depending on whether you just want to apply one function or, via some->, short-circuit a chain of function calls.

seancorfield20:11:03

(note: and won't work if you values can be false -- you'd need some-> in that case)

Thomas Tay21:11:27

Thanks guys, that was what I was looking for but somehow Google failed me

Michaël Salihi21:11:45

Hi! I tried to implement this following Java code in Clojure and I have a question about this keyword:

package hello;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
public class DBusHelloWorldServer implements DBusHelloWorldInterface
{
    private DBusConnection dbusConnection;
    public boolean isRemote()
    {
        return false;
    }
    private boolean stop=false;
    public String helloWorld(String name)
    {
        stop=true;
        return "Hello World : "+name;
    }
    public void start()
    {
        try
            {
                System.out.println(this);
                dbusConnection = DBusConnection.getConnection(DBusConnection.SESSION);
                dbusConnection.requestBusName("mon.premier.bus");
                dbusConnection.exportObject("/Main", this);
                while (!stop)
                    {
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {}
                    }
                dbusConnection.disconnect();
            }
        catch (DBusException e)
            {
                e.printStackTrace();
            }
    }
    public static void main(String[] args)
    {
        new DBusHelloWorldServer().start();
    }
}

Michaël Salihi21:11:42

In the try block, the exportObject take 2 arguments. The second one is referencing the current class with this. How can I interop?

Michaël Salihi21:11:34

Currently I had:

(defn start []
  (let [dbus-conn (. DBusConnection getConnection DBusConnection/SESSION)]
    (doto dbus-conn
        (.requestBusName "mon.premier.bus")
        (.exportObject "/Main" ?))))

Michaël Salihi21:11:19

I don't know what to replace the question mark with.

Michaël Salihi21:11:43

Do I have to use reify?

andy.fingerhut21:11:04

In the Java code, the this is passing the instance of class DBusHelloWorldServer from which the call to the exportObject method is being made. Presumably that because the dBusConnection object is going to do something with that object reference. Do you know what it will do with it? e.g. will it call some method on that object later? Save it in some data structure? Something else?

andy.fingerhut21:11:09

From within a normal Clojure function defined at the top level, there is no this enclosing object that makes sense to refer to. But if you create an object, as you suggest using reify , then inside of the reify you can specify an implementation for any number of methods you want, and all of their bodies will have access to a this reference for that object.

Michaël Salihi21:11:00

Maybe I must also implement the extended Interface with reify and reference it.

import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusInterface;
public interface DBusHelloWorldInterface extends DBusInterface
{
  @DBus.Description("Petit message de HelloWorld avec un nom en paramètre")
  public String helloWorld(String nom);
}

Michaël Salihi22:11:59

So yes, the object passed as the second argument needs to implement an interface which extends DBusInterface . You advise me to use gen-class or reify to extend an interface? How can I do that?

andy.fingerhut22:11:05

I believe it might look something like this with reify:

(ns some.ns
  (:import (some.package.here.where.this.interface.is.defined DBusHelloWorldInterface)))
   
(reify
  DBusHelloWorldInterface
  (isRemote [this]
    false)

  (helloWord [this name]
    (str "Hellow World : " name)))

Michaël Salihi05:11:20

@andy.fingerhut Perfect, thank you very much for your help! I'll try this.

Michaël Salihi12:11:09

Hi! If I want also rewrite this following interface part in Clojure, what's the options? proxy to extends DBusInterface?

import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusInterface;
public interface DBusHelloWorldInterface extends DBusInterface
{
  @DBus.Description("Petit message de HelloWorld avec un nom en paramètre")
  public String helloWorld(String nom);
}
I tried like this, but I get an error: Execution error (IllegalArgumentException) at main.core/eval14393 (REPL:24). No matching field found: hellowWorld for class main.core.proxy$java.lang.Object$DBusInterface$b57269b
(let [i (proxy [DBusInterface] []
          (helloWorld [] nil))]
  (.hellowWorld i))

andy.fingerhut14:11:22

There is definterface in Clojure, but it does not provide all of the options for creating interfaces that Java does. In particular, it isn't clear to me whether you can use it to create an interface that extends another interface.

andy.fingerhut14:11:32

Do you need to create a new interface for this?

Michaël Salihi15:11:18

Thanks for still being here and for the help @andy.fingerhut I appreciate. > Do you need to create a new interface for this? No, I don"t think so. I follow this tutorial https://artisan.karma-lab.net/mise-oeuvre-dbus-sous-java who can be simplified in one class implementing DBusInterface like that:

package hello;

import org.freedesktop.dbus.DBusInterface;
import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;

public class DBusHelloWorldServer implements DBusInterface
{
    private DBusConnection dbusConnection;
    private boolean stop=false;

    public boolean isRemote()
    {
        return false;
    }

    public String helloWorld(String name)
    {
        stop=true;
        return "Hello World : "+name;
    }

    public void start()
    {
        try
            {
                dbusConnection = DBusConnection.getConnection(DBusConnection.SESSION);
                dbusConnection.requestBusName("mon.premier.bus");
                dbusConnection.exportObject("/Main", this);
                while (!stop)
                    {
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {}
                    }
                dbusConnection.disconnect();
            }
        catch (DBusException e)
            {
                e.printStackTrace();
            }
    }

    public static void main(String[] args)
    {
        new DBusHelloWorldServer().start();
    }
} 

Michaël Salihi15:11:59

Now I still can't see how to pass the current reference to the exportObject method, because it must be a class that implements DBusInterface.

andy.fingerhut15:11:33

If you can find a way to do it in Clojure without using capabilities that definteface does not have, or if you can do it without using definterfaceat all, then great. There are cases in Clojure where to interface with some things in Java, you need capabilities that Java has of creating classes or interfaces that Clojure cannot do itself (because it wasn't designed to create those things with every possible option, only the ones judged most likely to be useful from a Clojure program). In such cases, I believe most Clojure developers write the smallest amount of Java code they can, compile it, and use those classes or interfaces from Clojure.

Michaël Salihi15:11:21

I feel like in this case I have to go through gen-class, right?

andy.fingerhut15:11:25

reify can be used to create a JVM object that implements one or more interfaces, including DBusInterface. It returns a reference to the new JVM object it creates. You can save that reference wherever you like.

andy.fingerhut15:11:06

Just as you can call new SomeClass() in Java and save the return value in any variable or field you want, and then use it later by that name.

andy.fingerhut15:11:07

All that code in the start method can be in a method that is within the class, but it could be in code outside of that class too, yes?

andy.fingerhut15:11:37

If it were in code outside of that class, instead of this you would pass a reference to the object to exportObject

andy.fingerhut15:11:44

Maybe there are benefits to using gen-class that are not clear to me at the moment, but I do not yet see a reason why gen-class is necessary.

Michaël Salihi15:11:30

This is what I tried first by following your advice but I had an output error. Wait, I'll try again and tell you the error message.

Michaël Salihi15:11:58

I get this error Execution error (DBusException) at org.freedesktop.dbus.Marshalling/recursiveGetDBusType (Marshalling.java:241). Exporting non-exportable type interface clojure.lang.IPersistentMap

Michaël Salihi15:11:10

The evaluated "reify object" #object[main.core$start$reify__149 0x4d666b41 main.core$start$reify__149@4d666b41]

andy.fingerhut15:11:34

Sorry, often I would be ready to jump in and try to reproduce this issue and think of alternatives, but I'm under some schedule pressure for work today, so not right now. Hopefully someone else may be able to help. There is also a #java channel that might be helpful in finding folks who often interoperate between Clojure and Java.

Michaël Salihi15:11:28

I understand, I thank you for all these advices! Have a good day.

andy.fingerhut22:11:47

Or rather, you would assign the return value from that (reify ...) expression to some variable somewhere, perhaps at the top level with (def dbus-hello-world-server (reify ...)), or (let [dbus-hello-world-server (reify ...)] ... code here that looks like code in your Java code's start() method ...)

👍 3
andy.fingerhut22:11:24

The code that you would write that looks like the Java's start method you showed would use dbus-hello-world-server instead of this

Michaël Salihi12:11:09

Hi! If I want also rewrite this following interface part in Clojure, what's the options? proxy to extends DBusInterface?

import org.freedesktop.DBus;
import org.freedesktop.dbus.DBusInterface;
public interface DBusHelloWorldInterface extends DBusInterface
{
  @DBus.Description("Petit message de HelloWorld avec un nom en paramètre")
  public String helloWorld(String nom);
}
I tried like this, but I get an error: Execution error (IllegalArgumentException) at main.core/eval14393 (REPL:24). No matching field found: hellowWorld for class main.core.proxy$java.lang.Object$DBusInterface$b57269b
(let [i (proxy [DBusInterface] []
          (helloWorld [] nil))]
  (.hellowWorld i))