The Prolog to Mercury transition guide

Similar documents
The Prolog to Mercury transition guide

Runtime support for region-based memory management in Mercury

The Mercury Frequently Asked Questions List

This lecture covers: Prolog s execution strategy explained more precisely. Revision of the elementary Prolog data types

G Programming Languages - Fall 2012

R13 SET Discuss how producer-consumer problem and Dining philosopher s problem are solved using concurrency in ADA.

simplicity hides complexity flow of control, negation, cut, 2 nd order programming, tail recursion procedural and declarative semantics and & or

1. true / false By a compiler we mean a program that translates to code that will run natively on some machine.

Programming Languages Third Edition

simplicity hides complexity flow of control, negation, cut, 2 nd order programming, tail recursion procedural and declarative semantics and & or

Lecture 5: Declarative Programming. The Declarative Kernel Language Machine. September 12th, 2011

Intermediate Code Generation

5. Semantic Analysis!

JVM ByteCode Interpreter

G Programming Languages Spring 2010 Lecture 4. Robert Grimm, New York University

The Metalanguage λprolog and Its Implementation

CSCC24 Functional Programming Scheme Part 2

Multi-paradigm Declarative Languages

Compiler Theory. (Semantic Analysis and Run-Time Environments)

Optimizing Closures in O(0) time

The Mercury Language Reference Manual

QUTE: A PROLOG/LISP TYPE LANGUAGE FOR LOGIC PROGRAMMING

CSE 413 Languages & Implementation. Hal Perkins Winter 2019 Structs, Implementing Languages (credits: Dan Grossman, CSE 341)

LOGIC AND DISCRETE MATHEMATICS

CMSC 331 Final Exam Section 0201 December 18, 2000

Determinism analysis in the Mercury compiler Fergus Henderson, Zoltan Somogyi and Thomas Conway Department of Computer Science University of Melbourne

Time : 1 Hour Max Marks : 30

5. Semantic Analysis. Mircea Lungu Oscar Nierstrasz

CMSC 330: Organization of Programming Languages. OCaml Imperative Programming

Index. Cambridge University Press Functional Programming Using F# Michael R. Hansen and Hans Rischel. Index.

G Programming Languages - Fall 2012

Implementação de Linguagens 2016/2017

6.001 Notes: Section 6.1

Logic Languages. Hwansoo Han

Logic Programming. Efficiency Issues. Temur Kutsia

The compilation process is driven by the syntactic structure of the program as discovered by the parser

Chapter 16. Logic Programming. Topics. Unification. Resolution. Prolog s Search Strategy. Prolog s Search Strategy

Prolog. Intro to Logic Programming

CS1622. Semantic Analysis. The Compiler So Far. Lecture 15 Semantic Analysis. How to build symbol tables How to use them to find

The PCAT Programming Language Reference Manual

Type Checking. Outline. General properties of type systems. Types in programming languages. Notation for type rules.

CSE 12 Abstract Syntax Trees

Polymorphic lambda calculus Princ. of Progr. Languages (and Extended ) The University of Birmingham. c Uday Reddy

Outline. General properties of type systems. Types in programming languages. Notation for type rules. Common type rules. Logical rules of inference

Contents. Chapter 1 SPECIFYING SYNTAX 1

Operational Semantics

What s different about Factor?

Semantic Analysis. Lecture 9. February 7, 2018

Project Compiler. CS031 TA Help Session November 28, 2011

COMP 181. Agenda. Midterm topics. Today: type checking. Purpose of types. Type errors. Type checking

Project 2: Scheme Interpreter

GNU ccscript Scripting Guide IV

Prolog. Logic Programming vs Prolog

INSTITUTE OF AERONAUTICAL ENGINEERING

Pace University. Fundamental Concepts of CS121 1

Z Notation. June 21, 2018

Absolute C++ Walter Savitch

COSC252: Programming Languages: Semantic Specification. Jeremy Bolton, PhD Adjunct Professor

CA Compiler Construction

PL Revision overview

C++11/14 Rocks. Clang Edition. Alex Korban

CMSC 330: Organization of Programming Languages. OCaml Imperative Programming

The Typed Racket Guide

COP4020 Programming Assignment 1 - Spring 2011

WACC Report. Zeshan Amjad, Rohan Padmanabhan, Rohan Pritchard, & Edward Stow

Lecture 21: Relational Programming II. November 15th, 2011

Introduction to predicate calculus

Part VI. Imperative Functional Programming

arxiv: v2 [cs.ai] 18 Sep 2013

Chapter 3. Semantics. Topics. Introduction. Introduction. Introduction. Introduction

Foundations of AI. 9. Predicate Logic. Syntax and Semantics, Normal Forms, Herbrand Expansion, Resolution

Acknowledgement. CS Compiler Design. Semantic Processing. Alternatives for semantic processing. Intro to Semantic Analysis. V.

Typed Racket: Racket with Static Types

Semantic Analysis and Type Checking

CSCI.6962/4962 Software Verification Fundamental Proof Methods in Computer Science (Arkoudas and Musser) Chapter p. 1/27

SEMANTIC ANALYSIS TYPES AND DECLARATIONS

MIDTERM EXAM (Solutions)

Weiss Chapter 1 terminology (parenthesized numbers are page numbers)

Chapter 9. Def: The subprogram call and return operations of a language are together called its subprogram linkage

More Non-logical Features of Prolog

Functional Notation and Lazy Evaluation in Ciao

Faculty of Electrical Engineering, Mathematics, and Computer Science Delft University of Technology

Introduction p. 1 Pseudocode p. 2 Algorithm Header p. 2 Purpose, Conditions, and Return p. 3 Statement Numbers p. 4 Variables p. 4 Algorithm Analysis

Packing sub-word-size arguments. Zoltan Somogyi

+ Abstract Data Types

Flang typechecker Due: February 27, 2015

Programming Paradigms

Logic Programming and Resolution Lecture notes for INF3170/4171

Tabling in Mercury: Design and Implementation

Code No: R Set No. 1

Part I Logic programming paradigm

Structured bindings with polymorphic lambas

Functional Programming. Pure Functional Programming

CHAPTER ONE OVERVIEW. 1.1 Continuation-passing style

Chapter 16. Logic Programming Languages

ASTs, Objective CAML, and Ocamlyacc

Lists. Michael P. Fourman. February 2, 2010

Implementing Subprograms

Motivation was to facilitate development of systems software, especially OS development.

Introduction to Erlang. Franck Petit / Sebastien Tixeuil

Transcription:

The Prolog to Mercury transition guide Version 14.01.1 Thomas Conway Zoltan Somogyi Fergus Henderson

Copyright c 1995 2014 The University of Melbourne. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies. Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided also that the entire resulting derived work is distributed under the terms of a permission notice identical to this one. Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions.

i Table of Contents 1 Introduction..................................... 1 2 Syntax and declarations........................ 1 3 Input and output............................... 1 4 Failure driven loops, assert and retract....... 3 5 Cuts and indexing.............................. 4 6 Accumulators and Difference lists............. 5 7 Determinism.................................... 6 8 All-solutions predicates......................... 6

Chapter 3: Input and output 1 1 Introduction This document is intended to help the reader translate existing Prolog programs to Mercury. We assume that the reader is familiar with Prolog. This guide should be used in conjunction with the Mercury User s Guide and Reference Manuals. If the Prolog code is quite declarative and does not make use of Prolog s non-logical constructions, the job of converting it to Mercury will usually be quite straight forward. However, if the Prolog program makes extensive use of non-logical constructions, conversion may be very difficult, and a direct transliteration may be impossible. Mercury code typically has a very different style to most Prolog code. 2 Syntax and declarations Prolog and Mercury have very similar syntax. Although there are a few differences, by and large if a program is accepted by a Prolog system, it will be accepted by Mercury. There are however a few extra operators defined by the Mercury term parser (see the Builtin Operators section of the Syntax chapter of the Mercury Language Reference Manual). In addition, Mercury implements both existential and universal quantification using the syntax and some Vars Goal all Vars Goal The constructor for lists in Mercury is [ ]/2, not./2. Terms with functor {}/N are treated slightly differently in Mercury than in ISO Prolog. ISO Prolog specifies that {1, 2, 3} is parsed as {} (, (1,, (2, 3))). In Mercury, it is parsed as {} (1, 2, 3). Mercury does not allow users to define their own operators. 3 Input and output Mercury is a purely declarative language. Therefore it cannot use Prolog s mechanism for doing input and output with side-effects. The mechanism that Mercury uses is the threading of an object that represents the state of the world through the computation. The type of this structure is io.state. The modes of the two arguments that are added to calls are di for destructive input and uo for unique output. The first means that the input variable must be the last reference to the original state of the world, and that the output variable will be the only reference to the state of the world produced by this predicate. Predicates that do input or output must have these arguments added. For example the Prolog predicate: write_total(total) :- write( The total is ), write(total), write(. ),

Chapter 3: Input and output 2 nl. in Mercury becomes :- pred write_total(int, io.state, io.state). :- mode write_total(in, di, uo) is det. write_total(total, IO0, IO) :- print("the total is ", IO0, IO1), print(total, IO1, IO2), print(., IO2, IO3), nl(io3, IO). Definite Clause Grammars (DCGs) are convenient syntactic sugar to use in such situations. The above clause can also be written write_total(total) --> print("the total is "), print(total), print(. ), nl. In DCGs, any calls (including unifications) that do not need the extra DCG arguments are escaped in the usual way by surrounding them in curly braces ( { } ). Note that in Mercury you normally use strings ("") rather than atoms ( ) for messages like "The total is". (It is possible to use atoms, but you have to declare each such atom before-hand, so it is more convenient to use strings.) However, for strings and characters, write prints out the quotes; to avoid this, you need to use print instead of write. Both write and print are defined in the io module in the Mercury standard library. One of the important consequences of our model for input and output is that predicates that can fail may not do input or output. This is because the state of the world must be a unique object, and each I/O operation destructively replaces it with a new state. Since each I/O operation destroys the current state object and produces a new one, it is not possible for I/O to be performed in a context that may fail, since when failure occurs the old state of the world will have been destroyed, and since bindings cannot be exported from a failing computation, the new state of the world is not accessible. In some circumstances, Prolog programs that suffer from this problem can be fixed by moving the I/O out of the failing context. For example ( solve(goal) -> ; ), where solve(goal) does some I/O can be transformed into valid Mercury in at least two ways. The first is to make solve deterministic and return a status:

Chapter 4: Failure driven loops, assert and retract 3 it: solve(goal, Result, IO6, IO7), ( Result = yes -> ; ), The other way is to transform solve so that all the input and output takes place outside io.write_string("calling: ", IO6, IO7), solve.write_goal(goal, IO7, IO8), ( solve(goal) -> io.write_string("succeeded\n", IO8, IO9), ; IO9 = IO8, ), 4 Failure driven loops, assert and retract Because Mercury is purely declarative, the goal Goal, fail is interchangeable with the goal fail, Goal. Also because it is purely declarative, there are no side effects to goals (see also the section on input and output). As a consequence of these two facts, it is not possible to write failure driven loops in Mercury. Neither is it possible to use predicates such as assert or retract. This is not the place to argue it, but we believe most programs that use failure driven loops, assert and retract to be less clear and harder to maintain than those that do not. The use of assert and retract should be replaced with a collection data structure threaded through the relevant part of the program. Data which is truly global may be stored in the io.state using the predicates io.get_globals and io.set_globals. These predicates take an argument of type univ, the universal type, so that by using type_to_univ and univ_to_type it is possible to store data of any type in the io.state. The standard library contains several abstract data types for storing collections, each of which will be useful for different classes of problems. The list ADT is useful if the order of the asserted facts is important. The set ADT is useful if the order is not important, and if the asserted facts are not key-value pairs. If the asserted facts are key-value pairs, you can choose among several ADTs, including map, bintree, rbtree, and tree234. We recommend the map ADT for generic use. Its current implementation is as a 234 tree (using tree234 ), but in the future it may change to a hash table, or a trie, or it may become a module that chooses among several implementation methods dynamically depending on the size and characteristics of the data.

Chapter 5: Cuts and indexing 4 Failure driven loops in Prolog programs should be transformed into ordinary tail recursion in Mercury. This does have the disadvantage that the heap space used by the failing clause is not reclaimed immediately but only through garbage collection, but we are working on ways to fix this problem. In any case, the transformed code is more declarative and hence easier to maintain and understand for humans and easier for the compiler to optimize. 5 Cuts and indexing The cut operator is not part of the Mercury language. In addition, the conditional operator -> ; does not do a hard cut across the condition only a soft cut which prunes away either the then goal or the else goal. If there are multiple solutions to the condition, they will all be found on backtracking. Prolog programs that use cuts and a catch-all clause should be transformed to use if-then-else. For example p(this, ) :-!, p(that, ) :-!, p(thing, ) :- should be rewritten as p(thing, ) :- ( Thing = this -> ; Thing = that -> ; ). The Mercury compiler does much better indexing than most Prolog compilers. Actually, the compiler indexes on all input variables to a disjunction (separate clauses of a predicate are merged into a single clause with a disjunction inside the compiler). As a consequence, the Mercury compiler indexes on all arguments. It also does deep indexing. That is, a predicate such as the following will be indexed. p([f(g(h)) Rest]) :- p([f(g(i)) Rest]) :- Since indexing is done on disjunctions rather than clauses, it is often unnecessary to introduce auxiliary predicates in Mercury, whereas in Prolog it is often important to do so for efficiency. If you have a predicate that needs to test all the functors of a type, it is better to use a disjunction instead of a chain of conditionals, for two reasons. First, if you add a new functor to a type, the compiler will still accept the now incomplete conditionals, whereas if you use a disjunction you will get a determinism error that pinpoints which part of the code

Chapter 6: Accumulators and Difference lists 5 needs changing. Second, in some situations the code generator can implement an indexed disjunction (which we call a switch) using binary search, a jump table or a hash table, which can be faster than a chain of if-then-elses. 6 Accumulators and Difference lists Mercury does not in general allow the kind of aliasing that is used in difference lists. Prolog programs using difference lists fall in to two categories programs whose data flow is leftto-right, or can be made left-to-right by reordering conjunctions (the Mercury compiler automatically reorders conjunctions so that all consumers of a variable come after the producer), and those that contain circular dataflow. Programs which do not contain circular dataflow do not cause any trouble in Mercury, although the implicit reordering can sometimes mean that programs which are tail recursive in Prolog are not tail recursive in Mercury. For example, here is a difference-list implementation of quick-sort in Prolog: qsort(l0, L) :- qsort_2(l0, L - []). qsort_2([], R - R). qsort_2([x L], R0 - R) :- partition(l, X, L1, L2), qsort_2(l1, R0 - R1), R1 = [X R2], qsort_2(l2, R2 - R). Due to an unfortunate limitation of the current Mercury implementation (partially instantiated modes don t yet work correctly), you need to replace all the - symbols with commas. However, once this is done, and once you have added the appropriate declarations, Mercury has no trouble with this code. Although the Prolog code is written in a way that traverses the input list left-to-right, appending elements to the tail of a difference list to produce the output, Mercury will in fact reorder the code so that it traverses the input list right-to-left and constructs the output list bottom-up rather than top-down. In this particular case, the reordered code is still tail recursive but it is tail-recursive on the first recursive call, not the second one! If the occasional loss of tail recursion causes efficiency problems, or if the program contains circular data flow, then a different solution must be adopted. One way to translate such programs is to transform the difference list into an accumulator. Instead of appending elements to the end of a difference list by binding the tail pointer, you simply insert elements onto the front of a list accumulator. At the end of the loop, you can call list.reverse to put the elements in the correct order if necessary. Although this may require two traversals of the list, it is still linear in complexity, and it probably still runs faster than the Prolog code using difference lists. In most circumstances, the need for difference lists is negated by the simple fact that Mercury is efficient enough for them to be unnecessary. Occasionally they can lead to a significant improvement in the complexity of an operation (mixed insertions and deletions from a long queue, for example) and in these situations an alternative solution should be sought (in the case of queues, the Mercury library uses the pair of lists proposed by Richard O Keefe).

Chapter 8: All-solutions predicates. 6 7 Determinism The Mercury language requires that the determinism of all predicates exported by a module be declared. The determinism of predicates that are local to a module may either be declared or inferred. By default, the compiler issues a warning message where such declarations are omitted, but this warning can be disabled using the --no-warn-missing-det-decls option if you want to use determinism inference. Determinism checking and inference is an undecidable problem in the general case, so it is possible to write programs that are deterministic, and have the compiler fail to prove the fact. The most important aspect of this problem is that the Mercury compiler only detects the clauses of a predicate (or the arms of a disjunction, in the general case) to be mutually exclusive (and hence deterministic) if they are distinguished by the unification of a variable (possibly renamed) with distinct functors in the different clauses (or disjuncts), so long as the unifications take place before the first call in the clause (or disjunct). In these cases, the Mercury compiler generates a switch (see the earlier section on indexing). If a switch has a branch for every functor on the type of the switching variable, then the switch cannot fail (though one or more of its arms may do so). The Mercury compiler does not do any range checking of integers, so code such as: factorial(0, 1). factorial(n, F) :- N > 0, N1 is N - 1, factorial(n1, F1), F is F1 * N. would be inferred nondeterministic. The compiler would infer that the two clauses are not mutually exclusive because it does not know about the semantics of >/2, and it would infer that the predicate as a whole could fail because the call to >/2 can fail. The general solution to such problems is to use an if-then-else: :- pred factorial(int, int). :- mode factorial(in, out) is det. factorial(n, F) :- ( N =< 0 -> F = 1 ; N1 is N - 1, factorial(n1, F1), F is F1 * N ). 8 All-solutions predicates. Prolog s various different all-solutions predicates (findall/3, bagof/3, and setof/3) all have semantic problems. Mercury has a different set of all-solutions predicates (solutions/2, solutions set/2, and unsorted solutions/2 all defined in the library module solutions )

Chapter 8: All-solutions predicates. 7 that address the problems of the Prolog versions. To avoid the variable scoping problems of the Prolog versions, rather than taking both a goal to execute and an aliased term holding the resulting value to collect, Mercury s all-solutions predicates take as input a single higher-order predicate term. The Mercury equivalent to is intersect(list1, List2, Intersection) :- setof(x, (member(x, List1), member(x, List2)), Intersection). intersect(list1, List2, Intersection) :- solutions((pred(x::out) is nondet :- (list.member(x, List1), list.member(x, List2))), Intersection). Alternately, this could also be written as intersect(list1, List2, Intersection) :- solutions(member_of_both(list1, List2), Intersection). :- pred member_of_both(list(t)::in, list(t)::in, T::out) is nondet. member_of_both(list1, List2, X) :- list.member(x, List1), list.member(x, List2). and in fact that s exactly how the Mercury compiler implements lambda expressions. The current implementation of solutions/2 is a zero-copy implementation, so the cost of solutions/2 is proportional the number of solutions, but independent of the size of the solutions. (This may change in future implementations.)