Fork me on GitHub
#clojure
<
2018-11-04
>
emccue05:11:49

What are some uses for core.logic / logic programming in general

emccue05:11:43

My interest is piqued, but I don't really know what I would use it for

andy.fingerhut05:11:39

It and the programming language Prolog, to which it is similar, have been described as allowing you to devote more time to describing the properties that a solution should satisfy, and then letting the "engine" or "runtime" determine how best to find a solution, so you get to specify more of the "what" of an answer vs. the "how to get the answer".

andy.fingerhut05:11:34

I haven't used that style of programming much myself, except for an exercise or three nearly 30 years ago in college, so haven't thought much about what I would use it for, but the Wikipedia page mentions natural language processing as one of the original areas of application, as well as expert systems, theorem provers, and automated planning.

andy.fingerhut05:11:01

It is a Turing complete programming language, so by no means restricted to those application areas.

schmee07:11:35

I think of logic programming like an in-memory query engine, i.e “find me a solution that satisfies all of these constraints at once”

☝️ 4
emccue14:11:26

I guess my disconnect is that I don't see how to connect it to real data

didibus23:11:48

Mostly useful for expert systems. Think AI. Or embedding of business rules. Like say you need to email customers a We'd love to see you back email. And you want to know when you should send this email. So maybe you need to send it when customer has purchased more than X amount, and has not logged in for longer than Y, and you have a credit card on file, and you don't find a newer account with the same email registered since, and they you did not send them such an email already.

didibus23:11:41

Now, imagine all the data for this was stored in a SQL DB. This could be a big SQL statement. But what if it is not saved in a single SQL DB, but the data is all over, and you need to call APIs, extract things from NoSql DBs, grab some of it from a SQL DB, derive some if it etc. So maybe you pull in all the data in memory, now how do you perform this logic over it? You don't have a SQL engine. Well one way is to use core.logic.

emccue14:11:47

All the examples I've seen have been logic puzzles

emccue14:11:38

I don't know how to give it input at runtime

roklenarcic16:11:44

If I have this is a macro:

`(let [a# 1 b# ~(str a#)])
How can I get that a# to evaluate to the actual symbol inside that ~() expression?

roklenarcic16:11:43

ah yes I must use gensym directly

tristefigure16:11:33

Maybe a string is not exactly what you want. You might be interested to deal with quoted code instead

(defmacro xxx []
  (let [a-sym (gensym "a-")]
    `(let [~a-sym 1 b# '~a-sym])))

; user=> (macroexpand '(xxx))
; (let* [a-7756 1 b__7748__auto__ (quote a-7756)])

roklenarcic16:11:04

str was just an example of transformation, it was far more complex otherwise

roklenarcic16:11:15

but I used gensym and that worked

seancorfield17:11:31

@emccue We use the unify part of core.logic at work in tests. We use it where we know some parts of a data structure that we expect in a test, but not others (such as generated IDs, date/time values etc) -- we can then assert that a test passed if it produced a data structure that can be unified with a pattern (for the known parts of the structure, with ?vars for the unknown/changeable parts).

didibus23:11:48

@U04V70XH6 Couldn't core.match work as well? Or is core.logic unification more powerful?

seancorfield23:11:46

I don't know enough about either to speak to that. I was just answering Ethan's question and happened to know one of my colleagues had used core.logic that way...

didibus00:11:06

Ah ok. I've actually been curious lately to compare core.logic unify, with core.unify and core.match so I was fishing for answers 😛

seancorfield00:11:21

Yeah, definitely an area I'd be interested to dig deeper into. I get the impression that core.match and core.unify were early and fairly specific functionality and core.logic is a later superset of both...? But I haven't dug into the history.

lwhorton00:11:12

i just started learning datomic and the first real blocker for me is trying to wrap my head around datalog, a logic programming query syntax

dpsutton18:11:12

Oh that is nice Sean

dpsutton18:11:48

I've walked dissoc over maps before but that sounds handy

roklenarcic18:11:50

There's a function I need a lot, but can't find it in standard library. Like (= x y) but where instead of returning true/false it would return x/nil. Is there anything like that?

mtnygard18:11:31

@roklenarcic (defmacro when= [a b] (when (= ~a ~b) ~a))

mtnygard18:11:57

I can't think of anything in clojure.core that does exactly what you want.

enforser18:11:00

I don't think I've ever seen a core function like that either!

enforser18:11:25

You need to wrap (when ...) in a backtick for the macro (defmacro when= [a b] (when (= a b) ~a))`

enforser18:11:52

but I would just just define it as a function... (defn when= [a b] (when (= a b) a))

seancorfield18:11:00

Also, if you do it as a macro that way, you need to watch out for ~a being evaluated twice

user=> (defmacro when= [a b] `(when (= ~a ~b) ~a))
#'user/when=
user=> (def n (atom 0))
#'user/n
user=> (when= (swap! n inc) 1)
2
user=> (defmacro when= [a b] `(let [a# ~a] (when (= a# ~b) a#)))
#'user/when=
user=> (def n (atom 0))
#'user/n
user=> (when= (swap! n inc) 1)
1
user=> 

seancorfield18:11:17

The function doesn't suffer from that

user=> (defn when= [a b] (when (= a b) a))
#'user/when=
user=> (def n (atom 0))
#'user/n
user=> (when= (swap! n inc) 1)
1
user=> 

seancorfield18:11:11

(the multiple evaluation thing with macros has caught me out several times!)

roklenarcic19:11:31

I mean, obviously I can write the macro myself, but I would have to release it as a library to include in all of my projects. That's why it's nicer if it's built in 😉

roklenarcic19:11:45

Another one that keeps appearing is (comp first filter)

enforser19:11:52

you need some

enforser19:11:12

oh, nevermind some works with (comp first keep)

roklenarcic19:11:17

like picking the first item in a sequence that matches a predicate, that's so basic i'm surprised it's not in library

andy.fingerhut19:11:13

For such short 'utility' functions/macros, I suspect many people simply copy a util.clj file from project to project, or try to add it to one of the existing public utility libraries like medley, useful, and one or two others I am forgetting right now.

andy.fingerhut19:11:21

Having seen the 'picking the first item in a sequence that matches a predicate' question for the second time this week, I suspect it maybe should be a Clojure FAQ why it isn't built in. It is by design, not accident, I believe.

dpsutton19:11:20

Alex mentioned a good rationale earlier this week that linear scan is a smell that you should have a different data structure

andy.fingerhut19:11:31

You can be very very very certain that its absence is not because they didn't think of the possibility of adding it.

seancorfield20:11:35

Even if a linear scan is all you can do, (first (filter ...)) is likely to suffer from chunking so you could get a lot more of your sequence realized than you want/expect and that might matter.

enforser20:11:14

this is something I've run into before (with the chunking) - where I wanted the laziness of seqs to control when side effects went off. Since most seqs evaluate in chunks of 32 it was doing far more than I was asking for at the moment.

(first (filter (fn [x] (prn x) (= 0 x)) (range 100)))
;; returns 0 but prints off all numbers from 0 to 31

enforser20:11:11

I ended up getting around the chunking by re-defining my sequence as an actual lazy seq

(defn my-range
  [& [x]]
  (lazy-seq (cons (or x 0) (my-range (inc (or x 0))))))

(first (filter (fn [x] (prn x) (= 0 x)) (take 100 (my-range))))
;;  stills returns 0 and doesn't evaluate anything beyond 0! 

andy.fingerhut21:11:15

laziness and control of side effects are a bomb waiting to go off.

enforser23:11:40

yeah, I ultimately ended up doing something else - making a map of functions to be executed instead of using laziness for side effects! Should have mentioned that earlier

ag21:11:48

can someone help me with this? https://clojureverse.org/t/writing-large-map-into-a-file/3130 I'm trying to write large map into a file, and for some reason it doesn't save the whole thing. It writes a map that ends with ellipsis like that {:key-n val-n, ...}. I tried switching from spit to this:

(with-open [w ( filename)]
      (binding [*out* w] (pr my-large-map)))
Yet that didn't help. What am I missing? I mean the map is not that big, it actually contains less than 200 elements, but the keys are very long strings. Could that be some kind repl/nrepl/CIDER limitation?

ag21:11:54

yes... that was it... thank you @rakyi

didibus23:11:48

Mostly useful for expert systems. Think AI. Or embedding of business rules. Like say you need to email customers a We'd love to see you back email. And you want to know when you should send this email. So maybe you need to send it when customer has purchased more than X amount, and has not logged in for longer than Y, and you have a credit card on file, and you don't find a newer account with the same email registered since, and they you did not send them such an email already.

didibus23:11:41

Now, imagine all the data for this was stored in a SQL DB. This could be a big SQL statement. But what if it is not saved in a single SQL DB, but the data is all over, and you need to call APIs, extract things from NoSql DBs, grab some of it from a SQL DB, derive some if it etc. So maybe you pull in all the data in memory, now how do you perform this logic over it? You don't have a SQL engine. Well one way is to use core.logic.