 Okay. Hello. I'm Mauro Lofis. I've used Clojure for many years now, and now that I came to the Singapore, I'm working at 10X but not with Clojure, but it's okay. Has anybody here heard of logic programming, CoreLogic? Has anybody used CoreLogic or Prolog? Okay. So it would be cool. It's just an introduction, so if you were advanced, it wouldn't be so fun, but it would be cool. So logic programming is also known as relational programming. I tested this. Okay. So it's relational programming. So we use relations as opposed to functions. So it's a bit of a difference from who are used to functional programming. It's declarative, so we don't tell the computer what to do or how to do it, just tell it what we want to see as the answer. We define the problem and it gives us the solution. We can consider it a higher level than imperative programming or functional programming. Because of this, it's closer to how a human would define the problem. You simply specify a set of facts and rules that manipulate those facts. And then you run the software with this input. Okay. So logic programming, probably the most famous implementation, or not implementation, but the most famous family of implementations is Prolog. It was created in 72, a long time ago. It was used in some practical systems, practical applications. When I was searching for this, the only two practical things I found were these two. So it was used in Windows NT for network configuration to match all the configurations to give you a valid output or to check that you have something valid. And it was also used for the first implementation of Erlang. And because of this, it influenced Erlang and its design. So even after they changed the implementation to some other, I think C++ or something, they still kept some Prolog concept. If you will see the language popularity index, it's actually Prolog is considered more popular than Clojure. Sounds strange, but that's what they say. It's more popular than Haskell, Kotlin, Lua, Erlang, Scheme. All of these Prologs did pretty well, but nobody seems to use it. It's only in this index, so I find it a bit suspicious, but okay. It also inspired Datalog, which is used in many Clojure libraries, in particular in Datomic. And Haskell and Datascript, which was based on Datomic. So you have heard of Datalog before, even if you haven't used CoreLogic. And just like this, Prolog was used for artificial intelligence in the old times before the AI winter. And it's good for symbolic manipulation and manipulation of symbols and data structures and symbolic mathematics. There are many implementations of Prolog, and some are pretty fast. So in spite of being very high level, they could manage to make it considerably fast, much faster than naive implementation. It's not super fast compared to other languages or something, but it's fast enough for many problems. And then there's the other family of logic programming, which is Minicarin, which is much more recent. It was created and released in the recent Schemer in 2005. It's a famous book. It's in the series of the little Schemer. There's a new one coming out this August, the little typer. I don't know if anyone has seen. It looks good. And it's much more minimalistic. So it's just a handful of primitives. And everything else is implemented using these primitives. So it's something like Scheme in this sense. And by the way, they use Scheme to implement the first version of Minicarin. It's pretty easy to implement. It fits on two printed pages on the book. And since it's easy, many people implement it as an exercise when they're reading this book, but they don't program any Scheme. They decide to implement their own favorite language, implement Minicarin. So there are dozens of implementations. Most of them are abandoned because they were used as an exercise, not as a real live project. It's much easier to embed than Prolog because it's much more lightweight. And you can implement your own language. It doesn't have to be an external process. And it's also much more functional, the internals of implementation. They don't have side effects. They don't have global states. They manipulate lazy sequences. So it's very different from Prolog. In Prolog, you change the state. And then when you see that you went to a wrong path, you change the state back. Not you as a user of Prolog, but the implementation of Prolog has to do that. So it's much more different from a functional program. Speaking specifically of CoreLogic, which is the Minicarin implementation enclosure. It was implemented by David Nolan, who implemented a bunch of closure libraries like OM and CoreMatch. CoreMatch is like one subset of CoreLogic. In 2010, and it's still going strong. The last commit was last year, which is not so recent. But it's much more recent than almost any other Minicarin implementation. And I think after some time, like after seven years of development, it's stable enough. The system is small enough that there is not so much that you would do. It has fixed mostly all the bugs. And there are many, oh, I didn't change here. Sorry. There are many closure libraries using CoreLogic. Many of them are kind of abandoned as well. But these three are active and they seem pretty cool. Kibbit is for static code analysis. So it detects that they are doing some pattern that could be done in a different way. So it recommends changes to your source code. And it's very easy to extend. You just create a new logic rule. This Barleyman is a smart text editor created by the creator of Minicarin. He is still actively working on it and exploring. It's pretty cool. You can define the unit test and it tries to give you some implementation of your source code. And then you see that the implementation seems wrong. Not because it is wrong, but because our tests are not complete. And then you add a new test and then it fixes the implementation until you are satisfied enough with the implementation. It's pretty cool. And these other library expressions for symbolic computation which is one of the things that logic programming excels at. So just to give a quick refresher, when I say relational as opposed to function, it's that school concept. If you have a function that maps things from your input to the output set, it's convenient for programming because for every input you need one output and not multiple outputs and not zero outputs. Except for exceptions, when your program, your function throws an exception, that's not exactly an output, but for the pure function, that's the idea. So you have the table or the diagram. A relation as opposed to that, you could also have the diagram, but it starts to get messy very fast. So we usually only have the table and it's not very clear which column is the input, which column is the output. So it's just a series of facts. So person X thinks that person Y likes person Z. So this is true. This sentence is true for all these triples, right? So this is a relation. Let's use the wrap a little bit. Yay. Now I'd like to, can I read? Yes. Can you read back there? Okay. Okay. So let's skip the imports. So let's say we declare a database with a relation parent. And we declare also the database of the facts, facts as in the rows in that table. So we can say that the parent of John is Bobby, another parent of Mary is also Bobby, so Bobby has two children. And a parent of Bobby is Susan. I wrote here with capital letter in the beginning and full capital to make it clear that this is older than this, which is older than this. A bit more intuitive. So let's add this over all the parents of John. So it gives us a list with all the parents of John, which is in this case is only Bobby, because we only have one fact stating someone's parent is John. But you remember that there is no clear input or output. So we can reverse the question here. So we want to know everyone whose parent is Bobby. Then it gives us all of Bobby's children. So there are two possible values for Q that satisfy this fact. Two values here that would satisfy this fact given the database of facts. Okay. If we run this without using the database, so now I'm running from this closing print not here. It will give us an empty set because the database is empty. We can define other relations. Relations can compose. So if we decide to define a sibling relation. So X and Y are siblings. When is this true? This true if this is fresh to introduce a new variable. So it's kind of a lead. So if there is some parent P, which is parent of X and also parent of Y. So X and Y have a parent in common. They are siblings, right? Now that I've defined this, let's see all siblings of John. I forgot this. Oh, okay. What is this? Thanks. So now all siblings of John and Mary. Okay. It's saying that the same person is sibling of itself. And that happens. Okay. Yeah. There is this not equal here. So they must have a parent in common and they must not be the same person. All right. That's important. Now it's only Mary. You can also define another as we see here. You see that we can compose the, we can reuse relations. You're using the parent relation that's a primitive defined in the database of X. So we can also state a grandparent. So a great parent of X is if you have some parent who is a parent of X and parent of this parent is a great grandparent, right? Now we can ask for all grandparents. Same thing again. So we can ask for all grandparents of, in the case Susan, you have some grandparent, some grandchild. So John and Mary are grandchildren of Susan. We can also ask who is the grandparent of John? Susan, who is the grandparent of some random person that doesn't exist, nobody. We can even ask, suppose we had two other variables, A and B. Now we want B to be the grandparent of A and Q to be the vector AB. So we want all the pairs of grandparents. So John and Susan is a pair of grandchild and grandparent and Mary, Susan's another pair. Okay, so this is the basic thing. There's also this other basic. Can I read it now? Yeah, okay. There are these primitive, not primitive but basic functions that are implemented in the core library of CoreLogic. Member O and append O. It, almost all goals have these O in the end. It's, in the book it's actually a small goal, like a small circle, like in degrees. But here it type is O. It's from here. So suppose we want all the members of this sequence, one, two, three. It will give us one and two and three. These are all the possible values that Q can have to be a member of this. If we specify that these two things must be true. So Q must be a member of one, two, three and also a member of three, four, two, zero. This will give us the intersection of the two lists, right? So this is a basic operation. I hear we show this source code. How to implement member O. It supports pattern matching and destructuring. So an element is a member of a list. If this list has a head and a tail and the head is exactly this element, then it's true. And it's also a member of the list. If it has a head and a tail, and it's a member of the tail. So you can have a recursive implementation. Recursive definition. A PEN DO is the same idea. So for an empty list, now let's first show how it works. So Q is the concatenation of these two lists, right? You can also change the input here. So you may want to see who do we have to concatenate to one and two to get one, two, three, four as the result. So Q must be three and four, right? So it's not clear who's the input, who's the output. Everything can be input or output. You can have no input at all and get all the relations, all the tuples that satisfy the question. You can specify everything if you want. So you can give three and four and just check if it's true or not. So yeah, so if it starts when it underscores, it means that the variable that you're asking is not bound, it's not ground, as they call it. Grounded. And it means that any value of Q would satisfy this relation. So we're just checking if this is true or not. If you change here, it will give us an empty set, which means that the input is false, it's impossible to satisfy. Let's see how a PENDLE works. So, yes. Can you tell me which input? Which input? What do you mean? Because if you change, you can change any of the inputs to make it true. So it's not clear which input is wrong. Not logically inputs then, you can look at it. Okay, like now you say, is this fixed one, two, is fixed two, element, this? If one and two is fixed. Element, is there a fixed size element, this? No, no, no, it could be any size. Oops. Right, right, right. So it should be here. You see? So the input could have any size. We could even generate it as a need is. We can use closure functions here, no problem. So it's empty, because I'm starting with zero here. Okay, so you can use closure data structures. I haven't tested with strings, but I think they should work. If the string doesn't work directly, you can convert it to a sequence and it should work. So for a pendo, so x concatenated to y equals z, if x is empty and then you'll concatenate with anything and get the same anything. So underscore means you're not matching with anything, but since it starts with y here in the argument list, in some y here, it will try to match. Or x has a head a and a tail d, y has something and the result must have the same head a and a, oh no, sorry. Oh yeah, yeah. X is a with tail d and y is this and z must be a with the tail r, so they must have the same tail and the tail r must be the result of d concatenated with y. So it says if you extracted the head of x and the tail of x, you just concatenate with y and that gives d. So, oops, not here. Okay, let's see one more example. So there is this zebra, zebra, oops, challenge. It's like a puzzle, like a newspaper puzzle. You have probably seen some variation of this. Can I, let me try to show here. This is the question, not the question, but the synthesis from the exercise. So there are five houses. The Englishman lives in the red house. Spaniard owns the dog. So it's this table of facts, but you don't know who owns the zebra. That's the question. So each person, each one of these five people have one color, one nationality, one drink, one smoke, one pet, and some of these facts are given in the input and you have to deduce all the other facts until you know who has the zebra. Okay, let's go back to source code. So these facts are almost a direct translation of those facts. So here we represent everything as a couple of five elements, nationality and something and the drink and then the, whatever, like the last one is the color. So a fact like this means that the person that drinks coffee lives in the greenhouse. So H.S. is the list of all the, it is the table that we will see in the end. So it's this table, it's a list of rows. So it's a list of side five and each list has five elements. It's a list of lists. So someone has milk. First in this list is the Norwegian person. Next to the Norwegian person is someone who lives in the blue house. Right to the person that lives in the ivory house is the one in the green house. It's exactly the facts there and the relations here are supposedly simple. They're not super complicated. So X, Y, they're right. Yeah, so the relation right, right all is X is, or Y is the right of X if they appear in this order, X and Y and then something else. Or if they don't appear in this order but in this something else, then recursively they appear in that list in that order. So inside the tail they are X, Y in that order, right? And next all will be similar. So either one is next to the, one is right to the other or the other is at the right of the one. First one. So we just run that. So here is the printed version of the output on the right hand side. So we see that we get five tuples, each one with five elements representing the solution. This thing here is unbound. So it could be anything. It's not specified by the input, but it's fine. And this one here is the zebra because actually the zebra is not mentioned in any of the initial sentences. So you just assume that if each person has one animal and this one has fox, this has a horse, then the zebra must be the last one, the only one that nobody has. And this runs extremely fast. I think it's three milliseconds. So you could use this for some more practical thing. Yes, okay. What else? Another, a bit more similar, but a bit more advanced example is a Sudoku solver. Has anybody, everybody played Sudoku? Okay. Okay, so I won't try to define the game, but it's basically the source code is the literal definition of the rules of the game. So we start with this. The input will be these hints. So when it's zero, it's actually a blank cell. If it has a number, it must be that number in the solution. So this is our input. How does it work? We call this LVAR function that creates a fresh variable. So we can give some value to this variable, but it starts without anything. Rows will be a list of the rows. I can show here how it will be done. So suppose that range of 81. Here, so we have the Ripple. So the Ripple can help us. So if we have that input, the rows will be this structure. So it'll be nine lists with nine elements each. Let's undo here. Then the columns will be, let's define. Oh, no idea. Okay, so let's put the result of the last evaluation in rows so that when we calculate columns, we can reuse rows here, right? Right, so this will be columns. So you see zero, one, two, three here in first column. Nine, 10, 11. So the Ripple is helping us to understand the code. The squares will be this. So with these positions, when a column function gets square, you just give it the x and y coordinate of one cell in the original, in the hints vector, and it will return you all the nine elements that start, that have the nine elements of the square that has that element as the top left cell. It doesn't make sense. So if we call square with L passing the position of this number seven, yeah, here, it will give us this square here. Right, oops. And now the rules of the game. So every G means that every element in the sequence that we press in the end must satisfy the goal that we have defined here. So here we have a goal that every number must be in this domain. So this, the finite domain thing is an extension to the corollogic. It's not a basic mini-caring thing, but it's an extension. It works, it's integrated with everything else. So we define that all numbers in this sequence, all numbers in all of our variables must have one of these numbers between one and nine. And then we have to initialize them with the variables. And then in every row, all the numbers must be distinct. In every column, all numbers must be distinct. And in all those squares, all the numbers must be distinct. This is pretty much the rules of the game. If we run this, it gives us the solution here, which if you see, it's the same as hints, but replacing zeros with numbers. So yeah, it works and it's super fast. So one good, there's one good talk about mincaring. About quines, do you know what that is? Have you watched that talk? Okay, so a quine in computer science is one program that receives as input that produces as output itself. It's own input. So I've seen two different definitions, so you can give it the same program as input and it has produced the same program. Or you don't pass any input and it must create as output the source code of the program. Does that make sense? Okay, so in that talk I was mentioning, they implemented one not parser, what's the word? One evaluator, one interpreter of source code, they defined the operations, they implemented the interpreter. So you could pass any input, any abstract syntax tree as in Lisp, so which is the result of parsing any program, but in Lisp it's the basic program, the source code, and it evaluates and gives you the output. And since it's all relational, it's not, since he's using logic programming, the input and the output are the same. So he can specify that the output must be the source code and then he can get all the programs that generate the self program as the output. So he has a quine generator in the end. Yeah, it's very crazy. There should be infinitely many, yes. Yes, yes, it will be infinitely many, but you can specify how many solutions you want to get. I didn't show you here, but here instead of, you can use run star and then get all the solutions or you can specify run one or run two and get only these many solutions and stop. So if you know that it will be infinitely many, you can specify one number and be happy with that. Okay, can you talk about the smallest solution or is it just the one that did it? You cannot guarantee that it's the smallest solution, but we usually find a small solution probably the smallest because it's the way the search works. It's like a depth first search. And I don't know, so it's not guaranteed to be this. Yeah, it's not guaranteed, anyway. But at least in the token, I was running all the solutions were small. Like you never got a huge program that generated itself. And this is similar to that library that was a smart text editor that I mentioned that you would give all the unit tests and it produces the program that generates that unit test. All of that by implementing an interpreter of the language. And then you get the program generator for free. I think that's the basic... Are there limits to what kind of operations you can use in that interpreter? I think there can be certain kinds of operations like constant. Right, yes, there are limits of operations. So for example, Mincari doesn't have this finite domain thing. So depending on the operation, you want to, depending on the constraint, you cannot declare that constraint in your program. I think about like maths things like given X, given a program that has X plus 10, can it find the value of X so that the result is free? Yes, it can do, yes. So it can find the reverse function, yes. Does that happen in general, or is it like a very restricted sort of domain? If it had, like finding inverse functions is sort of a hard problem. Right, right. Yeah, my guess is that it only works with basic operations like plus, minus. Yeah, I think if you use very advanced, if you use some function like logarithm or something, probably there is no way to represent that in logic programming. And another thing that's cool here is that you can, as opposed to prolog, you can use closure in the rest of your program. So if you have one rule-based engine in one part of your program, but you want to expose that as a web application or something, you can use a closure web framework and have this only in this part. If you use prolog, you can, there is probably some web framework in prolog, but I wouldn't want to use that. Or the other alternative is to have one prolog process and one web server and have some form of inter-process communication. But that will be so fun, so easy to implement using the same language everywhere. But what's the actual type of the relations? Like, the closure types? Yeah, what actually is it, is it a function, is it a? No, I think it returns a data structure that represents something that we would, it's a function actually, that's so cool. So it's a, yeah, it's a closure. I thought it was like a vector representing the constraints or something. Let's see. Let's see if that's because of every G is a separate thing or not. Now, see here, we cannot run this relation type member all next to all. We cannot simply evaluate them. We can add here, because here underscore is not defined, okay? So it's a function, we want to call it. Probably it receives the state, like a map or something. Then it's another function. It's strange. I see it must implement, the argument must implement a protocol here and our map doesn't implement, so we'll have to create that. So instead of running here, it will be easier to see the source code. When I say that we can use closure with this, doesn't have to be so separate as the web part and the logic part. Even when we are building the structure of the logic part, you can use, for example, here we are transposing the functions. There is a way to define a relation that transposes its input. But it will be much worse than just using the closure way of transposing the sequence in its input. So we can use, in parts of the logic engine, we can use closure to help corollogic. How many puzzles have you tried? How many puzzles have you tried? How many puzzles I have tried? All of these, that was the example. We can try different, if we just modify it, it will be unsolvable. We can try to make it easier, so at least this modification shouldn't make it impossible, right? Let's see what happens. Yeah, now there are two possible solutions. Okay, but if we want to try a totally different puzzle, it will take a while, we'll have to find that. There are some puzzles that I tried, like I didn't use the logic, but I tried some take like two seconds, but some take like two seconds. Yeah, there's different puzzles. Yeah, I'm not sure if there is some puzzle that is so hard that it will take two seconds, I think. I don't know, probably it's a different implementation. Using logic, I'm using algorithms. Right. But then this thing inside pulls down to some algorithm anyway, right? This logic that eventually said it's an algorithm, like search in the tree that was constructed over there. Yeah. What absolutely should it kind of take? It can tell, only if it finds all of them. So yeah, you cannot get the number of solutions without finding them all. I have to import around here, 45 solutions. Pretty fast. But actually by right, I think it will arrive somewhere in the Suthukku that you, the puzzle that, very puzzle is the one that only give you one solution. I have to... You want to modify it, right? You want to build the puzzle. So you are... No, no, I mean, I mean, that's just what I think the Suthus absorb rules. I mean, you have the puzzle, and the puzzle only can resolve into one solution. If you have two solution, that puzzle is not valid, you know? You still can generate, like you can generate 45 solutions, but that puzzle is not valid. So you have one puzzle that only can generate, have one solution to it, and that is the valid puzzle. Right, you want to have one rule like that so that the solver only... If you're writing a puzzle, you're writing a puzzle that I think, yes. But one thing is I think that the general rule for Suthukku is the puzzle that you have can only resolve to one solution. Okay, that's part of the... You have one puzzle that is generated into one and one that is discovered, and they have that number... Mm-hmm, yeah, I see. Will it be possible to write that in core.logic, find me or the puzzles that only have one solution? I think it should work if you'll use nested calls of this run function. It's a long time, I guess. Yeah, I'll take a while. If the specification of your problem that we're running in the solver calls another solver, and it counts the... There's one goal, one clause, that it's the length of a list must be equal to something. So you can use that. As long as you can call the solver nested. Yeah, any other question? Yes. Okay, so I think that's it. Okay, thank you.