What is "prolog style" in Scala? - scala

The Scala 3 reference at https://docs.scala-lang.org/scala3/reference/metaprogramming/compiletime-ops.html mentions some "Prolog-like programming style" possible with Scala 3 mataprogramming:
The problem so far was that the Prolog-like programming style of
implicit search becomes viral: Once some construct depends on implicit
search it has to be written as a logic program itself.
But they all keep the viral nature of implicit search programs based
on logic programming.
I did some search but understood only that it somehow abuses the Scala compile-time behavior, and that something in it resembles Prolog.
What is that "Prolog-like programming style" and how it works? What namely resembles Prolog? Does it work in Scala 3?

Here is a basic correspondence to get you started:
Prolog-Atoms correspond to ordinary types.
Prolog-Variables correspond to type parameters.
What's called "Functors" here corresponds to type constructors.
Stating facts corresponds to providing constant givens.
Making queries corresponds to summoning corresponding proof-terms.
Let's start with a very simple example where we have just one atom, one prolog-"functor", one fact, and one query: this example from Wikipedia
cat(tom).
?- cat(tom).
>> Yes
can be directly translated into
trait Tom // Atom 'Tom'
trait Cat[X] // Functor 'Cat' with arity = 1
// Fact: cat(tom).
given Cat[Tom] with {}
// Query: ?- cat(tom). (is Tom a Cat?)
val query = summon[Cat[Tom]] // type-checks = Yes
Prolog rules correspond to parameterized given-definitions, the antecedents on the right side correspond to the using-parameters in the parameter list. For example, the classical syllogism example expressed in Prolog as
man(socrates).
mortal(X) :- man(X).
?- mortal(socrates).
>> True
can be encoded with a parameterized given that uses Man[X] and produces a proof of Mortal[X]:
trait Socrates
trait Man[X]
trait Mortal[X]
given socratesIsAMan: Man[Socrates] with {}
given allMenAreMortal[X](using m: Man[X]): Mortal[X] with {}
val query = summon[Mortal[Socrates]]
You can use scala3-compiler -Xprint:typer to see the composite proof term that the compiler generated to prove that Socrates is Mortal:
allMenAreMortal[Socrates](socratesIsAMan)
Knowing how to encode rules allows you to encode the more complicated example from the Wikipedia:
mother_child(trude, sally).
father_child(tom, sally).
father_child(tom, erica).
father_child(mike, tom).
sibling(X, Y) :- parent_child(Z, X), parent_child(Z, Y).
parent_child(X, Y) :- father_child(X, Y).
parent_child(X, Y) :- mother_child(X, Y).
?- sibling(sally, erica).
>> Yes
as follows:
trait Trude
trait Sally
trait Tom
trait Erica
trait Mike
trait FatherChild[F, C]
trait MotherChild[M, C]
trait ParentChild[P, C]
trait Sibling[X, Y]
given MotherChild[Trude, Sally] with {}
given FatherChild[Tom, Sally] with {}
given FatherChild[Tom, Erica] with {}
given FatherChild[Mike, Tom] with {}
given [X, Y, Z](using pczx: ParentChild[Z, X], pczy: ParentChild[Z, Y])
: Sibling[X, Y] with {}
given fatherhoodImpliesParentship[X, Y](using fc: FatherChild[X, Y])
: ParentChild[X, Y] with {}
given motherhoodImpliesParentship[X, Y](using mc: MotherChild[X, Y])
: ParentChild[X, Y] with {}
val query = summon[Sibling[Erica, Sally]] // Yes
Here, the compiler will generate a proof term that explains that Erica and Sally are Siblings because they have the same father Tom:
given_Sibling_X_Y[Erica, Sally, Tom](
fatherhoodImpliesParentship[Tom, Erica](given_FatherChild_Tom_Erica),
fatherhoodImpliesParentship[Tom, Sally](given_FatherChild_Tom_Sally)
)
More generally, conjunctions are encoded by multiple using-parameters, and disjunctions are encoded by multiple givens with the same result type:
// We can write "X /\ Y" as infix operator for conjunction
case class /\[A, B](a: A, b: B)
// We can write "X \/ Y" as infix operator for disjunctions
enum \/[+A, +B]:
case Inl(a: A)
case Inr(b: B)
// Inference for conjunctions: multiple parameters in `using`
given [A, B](using a:A, b: B): (A /\ B) = /\(a, b)
// Inference for disjunctions: multiple rules
given [A](using a: A): \/[A, Nothing] = \/.Inl(a)
given [B](using b: B): \/[Nothing, B] = \/.Inr(b)
// Example:
trait X
trait Y
trait Z
trait W
given X with { override def toString = "X" }
given W with { override def toString = "W" }
#main def query(): Unit =
println(summon[(X \/ Y) /\ (Z \/ W)])
// Finds a proof and prints `/\(Inl(X), Inr(W))`.
Since Scala 3, there is even negation available through util.NotGiven:
import scala.util.NotGiven
trait X
trait Y
trait /\[X, Y]
given X with {}
given [A, B](using a: A, b: B): /\[A, B] with {}
// Fails if we add `given Y with {}`
val query = summon[X /\ NotGiven[Y]]
Scala 3 adds a whole bunch of stuff on top of that, such as tuples (which are basically type-level lists) or computing with numeric / boolean / string singleton types, but I don't want to go too deeply into the details here.
Instead, I'd like to conclude by briefly sketching how it all fits into the landscape. The interesting difference between Prolog and Scala's type system is that the Scala compiler actually generates proof terms, and unlike in Prolog (where you get a simple "Yes"/"No"), those proof terms can carry around arbitrarily complicated computational content.
You might have noticed that in the examples above, the with {} mostly remained empty. This is usually not the case in the real code, quite the contrary: in the real code, you usually have some non-trivial definitions in the body of every given ... with { ... }. The reason why one is writing all those facts and inference rules is not for solving logical puzzles and obtaining "Yes" / "No" answers, but for generating huge complicated proof terms that do useful stuff.
The way it works is usually as follows:
Suppose you want to obtain a thing X
You import some big-&-smart library that knows how to construct a variety of things similar to the desired X
You use the "predicates" ( = typeclasses) / facts / rules ( = givens) of that library to express very precisely the properties that you want the thing X to have
If your description is precise enough, the library & the Scala compiler is able to summon the thing X purely from its type description.
So, in your average programming language, you have to write out all the terms manually. In Scala 3, you can instead specify the desired properties of the desired term through types, and the compiler will use this Prolog-like term inference system to summon a term with the right properties (given the right libraries, that is).

Related

how to normalize a `scala.reflect.api.Types.Type`

How to implement the function normalize(type: Type): Type such that:
A =:= B if and only if normalize(A) == normalize(B) and normalize(A).hashCode == normalize(B).hashCode.
In other words, normalize must return equal results for all equivalent Type instances; and not equal nor equivalent results for all pair of non equivalent inputs.
There is a deprecated method called normalize in the TypeApi, but it does not the same.
In my particular case I only need to normalize types that represent a class or a trait (tpe.typeSymbol.isClass == true).
Edit 1: The fist comment suggests that such a function might not be possible to implement in general. But perhaps it is possible if we add another constraint:
B is obtained by navigating from A.
In the next example fooType would be A, and nextAppliedType would be B:
import scala.reflect.runtime.universe._
sealed trait Foo[V]
case class FooImpl[V](next: Foo[V]) extends Foo[V]
scala> val fooType = typeOf[Foo[Int]]
val fooType: reflect.runtime.universe.Type = Foo[Int]
scala> val nextType = fooType.typeSymbol.asClass.knownDirectSubclasses.iterator.next().asClass.primaryConstructor.typeSignature.paramLists(0)(0).typeSignature
val nextType: reflect.runtime.universe.Type = Foo[V]
scala> val nextAppliedType = appliedType(nextType.typeConstructor, fooType.typeArgs)
val nextAppliedType: reflect.runtime.universe.Type = Foo[Int]
scala> println(fooType =:= nextAppliedType)
true
scala> println(fooType == nextAppliedType)
false
Inspecting the Type instances with showRaw shows why they are not equal (at least when Foo and FooImpl are members of an object, in this example, the jsfacile.test.RecursionTest object):
scala> showRaw(fooType)
val res2: String = TypeRef(SingleType(SingleType(SingleType(ThisType(<root>), jsfacile), jsfacile.test), jsfacile.test.RecursionTest), jsfacile.test.RecursionTest.Foo, List(TypeRef(ThisType(scala), scala.Int, List())))
scala> showRaw(nextAppliedType)
val res3: String = TypeRef(ThisType(jsfacile.test.RecursionTest), jsfacile.test.RecursionTest.Foo, List(TypeRef(ThisType(scala), scala.Int, List())))
The reason I need this is difficult to explain. Let's try:
I am developing this JSON library which works fine except when there is a recursive type reference. For example:
sealed trait Foo[V]
case class FooImpl[V](next: Foo[V]) extends Foo[V]
That happens because the parser/appender it uses to parse and format are type classes that are materialized by an implicit macro. And when an implicit parameter is recursive the compiler complains with a divergence error.
I tried to solve that using by-name implicit parameter but it not only didn't solve the recursion problem, but also makes many non recursive algebraic data type to fail.
So, now I am trying to solve this problem by storing the resolved materializations in a map, which also would improve the compilation speed. And that map key is of type Type. So I need to normalize the Type instances, not only to be usable as key of a map, but also to equalize the values generated from them.
If I understood you well, any equivalence class would be fine. There is no preference.
I suspect you didn't. At least "any equivalence class would be fine", "There is no preference" do not sound good. I'll try to elaborate.
In math there is such construction as factorization. If you have a set A and equivalence relation ~ on this set (relation means that for any pair of elements from A we know whether they are related a1 ~ a2 or not, equivalence means symmetricity a1 ~ a2 => a2 ~ a1, reflexivity a ~ a, transitivity a1 ~ a2, a2 ~ a3 => a1 ~ a3) then you can consider the factor-set A/~ whose elements are all equivalence classes A/~ = { [a] | a ∈ A} (the equivalence class
[a] = {b ∈ A | b ~ a}
of an element a is a set consisting of all elements equivalent (i.e. ~-related) to a).
The axiom of choice says that there is a map (function) from A/~ to A i.e. we can select a representative in every equivalence class and in such way form a subset of A (this is true if we accept the axiom of choice, if we don't then it's not clear whether we get a set in such way). But even if we accept the axiom of choice and therefore there is a function A/~ -> A this doesn't mean we can construct such function.
Simple example. Let's consider the set of all real numbers R and the following equivalence relation: two real numbers are equivalent r1 ~ r2 if their difference is a rational number
r2 - r1 = p/q ∈ Q
(p, q≠0 are arbitrary integers). This is an equivalence relation. So it's known that there is a function selecting a single real number from any equivalence class but how to define this function explicitly for a specific input? For example what is the output of this function for the input being the equivalence class of 0 or 1 or π or e or √2 or log 2...?
Similarly, =:= is an equivalence relation on types, so it's known that there is a function normalize (maybe there are even many such functions) selecting a representative in every equivalence class but how to prefer a specific one (how to define or construct the output explicitly for any specific input)?
Regarding your struggle against implicit divergence. It's not necessary that you've selected the best possible approach. Sounds like you're doing some compiler work manually. How do other json libraries solve the issue? For example Circe? Besides by-name implicits => there is also shapeless.Lazy / shapeless.Strict (not equivalent to by-name implicits). If you have specific question about deriving type classes, overcoming implicit divergence maybe you should start a different question about that?
Regarding your approach with HashMap with Type keys. I'm still reminding that we're not supposed to rely on == for Types, correct comparison is =:=. So you should build your HashMap using =:= rather than ==. Search at SO for something like: hashmap custom equals.
Actually I guess your normalize sounds like you want some caching. You should have a type cache. Then if you asked to calculate normalize(typ) you should check whether in the cache there is already a t such that t =:= typ. If so you should return t, otherwise you should add typ to the cache and return typ.
This satisfies your requirement: A =:= B if and only if normalize(A) == normalize(B) (normalize(A).hashCode == normalize(B).hashCode should follow from normalize(A) == normalize(B)).
Regarding transformation of fooType into nextAppliedType try
def normalize(typ: Type): Type = typ match {
case TypeRef(pre, sym, args) =>
internal.typeRef(internal.thisType(pre.typeSymbol), sym, args)
}
Then normalize(fooType) == nextAppliedType should be true.

What kind of morphism is `filter` in category theory?

In category theory, is the filter operation considered a morphism? If yes, what kind of morphism is it? Example (in Scala)
val myNums: Seq[Int] = Seq(-1, 3, -4, 2)
myNums.filter(_ > 0)
// Seq[Int] = List(3, 2) // result = subset, same type
myNums.filter(_ > -99)
// Seq[Int] = List(-1, 3, -4, 2) // result = identical than original
myNums.filter(_ > 99)
// Seq[Int] = List() // result = empty, same type
One interesting way of looking at this matter involves not picking filter as a primitive notion. There is a Haskell type class called Filterable which is aptly described as:
Like Functor, but it [includes] Maybe effects.
Formally, the class Filterable represents a functor from Kleisli Maybe to Hask.
The morphism mapping of the "functor from Kleisli Maybe to Hask" is captured by the mapMaybe method of the class, which is indeed a generalisation of the homonymous Data.Maybe function:
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
The class laws are simply the appropriate functor laws (note that Just and (<=<) are, respectively, identity and composition in Kleisli Maybe):
mapMaybe Just = id
mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
The class can also be expressed in terms of catMaybes...
catMaybes :: Filterable f => f (Maybe a) -> f a
... which is interdefinable with mapMaybe (cf. the analogous relationship between sequenceA and traverse)...
catMaybes = mapMaybe id
mapMaybe g = catMaybes . fmap g
... and amounts to a natural transformation between the Hask endofunctors Compose f Maybe and f.
What does all of that have to do with your question? Firstly, a functor is a morphism between categories, and a natural transformation is a morphism between functors. That being so, it is possible to talk of morphisms here in a sense that is less boring than the "morphisms in Hask" one. You won't necessarily want to do so, but in any case it is an existing vantage point.
Secondly, filter is, unsurprisingly, also a method of Filterable, its default definition being:
filter :: Filterable f => (a -> Bool) -> f a -> f a
filter p = mapMaybe $ \a -> if p a then Just a else Nothing
Or, to spell it using another cute combinator:
filter p = mapMaybe (ensure p)
That indirectly gives filter a place in this particular constellation of categorical notions.
To answer are question like this, I'd like to first understand what is the essence of filtering.
For instance, does it matter that the input is a list? Could you filter a tree? I don't see why not! You'd apply a predicate to each node of the tree and discard the ones that fail the test.
But what would be the shape of the result? Node deletion is not always defined or it's ambiguous. You could return a list. But why a list? Any data structure that supports appending would work. You also need an empty member of your data structure to start the appending process. So any unital magma would do. If you insist on associativity, you get a monoid. Looking back at the definition of filter, the result is a list, which is indeed a monoid. So we are on the right track.
So filter is just a special case of what's called Foldable: a data structure over which you can fold while accumulating the results in a monoid. In particular, you could use the predicate to either output a singleton list, if it's true; or an empty list (identity element), if it's false.
If you want a categorical answer, then a fold is an example of a catamorphism, an example of a morphism in the category of algebras. The (recursive) data structure you're folding over (a list, in the case of filter) is an initial algebra for some functor (the list functor, in this case), and your predicate is used to define an algebra for this functor.
In this answer, I will assume that you are talking about filter on Set (the situation seems messier for other datatypes).
Let's first fix what we are talking about. I will talk specifically about the following function (in Scala):
def filter[A](p: A => Boolean): Set[A] => Set[A] =
s => s filter p
When we write it down this way, we see clearly that it's a polymorphic function with type parameter A that maps predicates A => Boolean to functions that map Set[A] to other Set[A]. To make it a "morphism", we would have to find some categories first, in which this thing could be a "morphism". One might hope that it's natural transformation, and therefore a morphism in the category of endofunctors on the "default ambient category-esque structure" usually referred to as "Hask" (or "Scal"? "Scala"?). To show that it's natural, we would have to check that the following diagram commutes for every f: B => A:
- o f
Hom[A, Boolean] ---------------------> Hom[B, Boolean]
| |
| |
| |
| filter[A] | filter[B]
| |
V ??? V
Hom[Set[A], Set[A]] ---------------> Hom[Set[B], Set[B]]
however, here we fail immediately, because it's not clear what to even put on the horizontal arrow at the bottom, since the assignment A -> Hom[Set[A], Set[A]] doesn't even seem functorial (for the same reasons why A -> End[A] is not functorial, see here and also here).
The only "categorical" structure that I see here for a fixed type A is the following:
Predicates on A can be considered to be a partially ordered set with implication, that is p LEQ q if p implies q (i.e. either p(x) must be false, or q(x) must be true for all x: A).
Analogously, on functions Set[A] => Set[A], we can define a partial order with f LEQ g whenever for each set s: Set[A] it holds that f(s) is subset of g(s).
Then filter[A] would be monotonic, and therefore a functor between poset-categories. But that's somewhat boring.
Of course, for each fixed A, it (or rather its eta-expansion) is also just a function from A => Boolean to Set[A] => Set[A], so it's automatically a "morphism" in the "Hask-category". But that's even more boring.
filter can be written in terms of foldRight as:
filter p ys = foldRight(nil)( (x, xs) => if (p(x)) x::xs else xs ) ys
foldRight on lists is a map of T-algebras (where here T is the List datatype functor), so filter is a map of T-algebras.
The two algebras in question here are the initial list algebra
[nil, cons]: 1 + A x List(A) ----> List(A)
and, let's say the "filter" algebra,
[nil, f]: 1 + A x List(A) ----> List(A)
where f(x, xs) = if p(x) x::xs else xs.
Let's call filter(p, _) the unique map from the initial algebra to the filter algebra in this case (it is called fold in the general case). The fact that it is a map of algebras means that the following equations are satisfied:
filter(p, nil) = nil
filter(p, x::xs) = f(x, filter(p, xs))

Scala : EndoMonoids Function composition and Associativity Rules

I was going through the laws that govern Monoids and one of the two laws state that the append operation must be associative. For function composition this means that for all functions X->X given there are 3 functions f,g and h (f∘g)∘h=f∘(g∘h)
In scalaz I see that there is a type called EndoMonoid and it uses compose for appends which is different from the way normal function compositions work
val f : Int => Int = x => x*x
val g : Int => Int = y => y + 1
val e = f.endo |+| g.endo
val d = g.endo |+| f.endo
e run 10
Int = 121
d run 10
Int = 101
As can be seen from the above results that the functions don't satisfy the associative property. Does this mean that not all functions of type X -> X is a monoid?
What you claim cannot be seen from your example.
Your example only proves that function composition is not commutative. But function composition never was supposed to be commutative: if it were commutative, all math and programming would catastrophically collapse to counting the number of occurrences of basic operations (that is, if "counting" itself would somehow survive that... I'm not sure whether it's possible).
To demonstrate an example of associativity, you need a third function h: Int => Int, and then you have to compare
(f.endo |+| g.endo) |+| h.endo
vs.
f.endo |+| (g.endo |+| h.endo)
exactly as the rule (that you yourself have just quoted!) states.
Every set of endomorphisms is always a monoid, because categories are essentially just "monoids with many objects", whereas monoids are just "categories with a single object". If you take any category and then look at the endomorphisms at a single object, you automatically, by definition, obtain a monoid. That's in particular true for the canonical ambient "category" of ordinary functions. [Here should come the usual disclaimer that it's not really a category, and that in every real programming language nothing is really true]

Interpreting a list of free monads vs. interpreting a free monad of a list

I'm learning functional programming and have some (maybe obvious, but not for me :) ) question about monad. Every monad is an applicative functor. Applicative functor in turn can be defined as a higher-kinded type as follows (pure method omitted):
trait ApplicativeFunctor[F[_]]{
def ap[A](fa: F[A])(f: F[A => B]): F[B]
}
As far as I understand this typeclass means that we can take two values of F[A], F[B] and a function (A, B) => C and construct F[C].
This property enables us to construct the list reversing function:
def reverseApList[F[_]: ApplicativeFunctor, A](lst: List[F[A]]): F[List[A]]
Let we have
trait SomeType[A]
Now consider
type MyFree[A] = Free[SomeType, A]
val nt: NaturalTransformation[SomeType, Id]
val lst: List[MyFree[Int]]
QUESTION: Why are lst.map(_.foldMap(nt)) and reverseApList(lst).foldMap(nt) the same? Is it following from applicative functor laws or there is another reason? Can you please explain?
It follows from the laws of Traversable functors.
First, realize that _.foldMap(nt) is itself a natural transformation from MyFree to Id. Moreover, by the very definition of what it means to be a free monad, it is required to be a monad homomorphism1 (for any nt).
Let's start from your
reverseApList(lst).foldMap(nt)
which can also be written as
lst.sequence.foldMap(nt)
Now we are going to apply the naturality law of Traversable functors, with _.foldMap(nt) as the natural transformation nat. For it to be applicable, our natural transformation has to be an applicative homomorphism, which is expressed by the two extra conditions. But we already know that our natural transformation is a monad homomorphism, which is even stronger (preserves more structure) than an applicative homomorphism. We may therefore proceed to apply this law and obtain
lst.map(_.foldMap(nt)).sequence : Id[List[Int]]
Now using just the laws in the linked scalaz file it is provable (although in a roundabout way) that this last sequence through Id is actually a no-op. We get
lst.map(_.foldMap(nt)) : List[Id[Int]]
which is what we wanted to show.
1 : A natural transformation h: M ~> N is a monad homomorphism if it preserves the monadic structure, i.e. if it satisfies
for any a: A:
h(Monad[M].point[A](a)) = Monad[N].point[A](a)
for any ma: M[A] and f: A => M[B]:
h(ma.flatMap(f)) = h(ma).flatMap(a => h(f(a)))

Functor for sequence computation

I am reading scala cats book from underscore.io. It says following about Monad and Functor:
While monads and functors are the most widely used sequencing data
types..
I can see that Monad is using for sequencing data but Functor not at all. Could someone please show about sequencing computation on functors?
Seq(1, 2, 3).map(_ * 2).map(_.toString).foreach(println)
Here: you have a sequence of operations on a sequence of data.
Every monad is actually a functor, because you could implement map with flatMap and unit/pure/whatever your implementation calls it. So if you agree that monads are "sequencing data types", then you should agree on functors being them too.
Taken out of context, this statement is less clear than it could be.
A fuller version of the quote is:
While monads and functors are the most widely used sequencing data types
[...], semigroupals and applicatives are the most general.
The goal of this statement is not to erase the difference between functorial and monadic notions of "sequencing", but rather to contrast them with obviously non-sequential operations provided by Semigroupal.
Both Functor and Monad do support (different) kinds of "sequencing".
Given a value x of type F[X] for some functor F and some type X, we can "sequence" pure functions
f: X => Y
g: Y => Z
like this:
x map f map g
You can call this "sequencing", at least "elementwise". The point is that g has to wait until f produces at least a single y of type Y in order to do anything useful. However, this does not mean that all invocations of f must be finished before g is invoked for the first time, e.g. if x is a long list, one could process each element in parallel - that's why I called it "elementwise".
With monads that represent monadic effects, the notion of "sequencing" is usually taken a bit more seriously. For example, if you are working with a value x of type M[X] = Writer[Vector[String], X], and you have two functions with the "writing"-effect
f: X => M[Y]
g: Y => M[Z]
and then you sequence them like this:
x flatMap f flatMap g
you really want f to finish completely, until g begins to write anything into the Vector[String]-typed log. So here, this is literally just "sequencing", without any fine-print.
Now, contrast this with Semigroupal.
Suppose that F is semigroupal, that is, we can always form a F[(A,B)] from F[A] and F[B]. Given two functions
f: X => F[A]
g: X => F[B]
we can build a function
(x: X) => product(f(x), g(x))
that returns results of type F[(A, B)]. Note that now f and g can process x completely independently: whatever it is, it is definitely not sequencing.
Similarly, for an Applicative F and functions
f: A => X
g: B => Y
c: (X, Y) => Z
and two independent values a: F[A], b: F[B], you can process a and b completely independently with f and g, and then combine the results in the end with c into a single F[Z]:
map2(a, b){ (a, b) => c(f(a), g(b)) }
Again: f and g don't know anything about each other's inputs and outputs, they work completely independently until the very last step c, so this is again not "sequencing".
I hope it clarifies the distinction somewhat.