Research Summary on Implementing Functional Patterns by Synthesizing Inverse Functions
FF. Ricca, A. Russo et al. (Eds.): Proc. 36th International Conferenceon Logic Programming (Technical Communications) 2020 (ICLP 2020)EPTCS 325, 2020, pp. 296–302, doi:10.4204/EPTCS.325.39
Research Summary onImplementing Functional Patterns bySynthesizing Inverse Functions
Finn Teegen
Kiel UniversityKiel, Germany [email protected]
In this research summary we present our recent work on implementing functional patterns with in-verse functions in the lazy functional-logic programming language Curry. Our goal is the synthesis ofthe inverse of any given function in Curry itself. The setting of a functional-logic language especiallyallows the inversion of non-injective functions. In general, inverse computation is a non-trivial prob-lem in lazy programming languages due to their non-strict semantics. We are so far able to directlyderive the inverse function for a limited class of functions, namely those consisting of rules that donot involve both extra variables and non-linear right-hand sides. Because the synthesized definitionsare based on standard code, known optimizations techniques can be applied to them. For all otherfunctions we can still provide an inverse function by using non-strict unification.
Curry [18] is a functional-logic programming language [7] with Haskell-like syntax and support for non-deterministic operations [15] and free variables. In standard code only variables and constructors areallowed in patterns on the left-hand side of function definitions. Functional patterns [6] are an extensionto Curry and relax this restriction by additionally allowing the use of function symbols within patterns.As an example, we consider the function that returns the last element of a list. last :: [ a ] -> alast ( xs ++[ x ]) = x
Here, we use the function (++) , which concatenates two lists, to specify that the variable x should cor-respond to the last element of the input list. Semantically, the functional pattern is equivalent to thefollowing infinite set of rules. last [ x ] = xlast [ x1 , x ] = xlast [ x1 , x2 , x ] = x . . . Note that the definition of last using functional patterns is less strict than the following common encod-ing with an equality constraint, where (=:=) denotes the (strict) unification operator. last z | xs ++ [ x ] =:= z = x where xs , x free
For example, evaluating the expression last [failed, True] (where failed is an expression that hasno value) fails with the definition above due to the strictness of the unification operation. In contrast, thevariant based on functional patterns yields
True for the same expression and, thus, properly reflects thenon-strict nature of Curry, which can benefit many applications in practice [11, 12]..Teegen 297Despite their usefulness, functional patterns have the disadvantage that they must be explicitly sup-ported by the runtime system. For this purpose, the runtime system has to provide a special primitive fornon-strict unification [6] with which functional patterns are implemented. With this primitive, namely (=:<=) , the last function using functional patterns can be translated as follows. last z | xs ++ [ x ] =: <= z = x where xs , x free
Our original research intention was to find a way to implement functional patterns as a source-to-sourcetransformation and, thus, without any dedicated primitive. This way, not only the complexity of theruntime systems of different compilers could be reduced, but functional patterns could also be madeavailable in other programming languages with similar characteristics, e.g., TOY [19].Due to the close connection between function inversion and functional patterns [10], it seemed likelythat functional patterns could also be expressed by means of function inversion. In fact, both functionalpatterns and inverse functions can be expressed by one another. For instance, we can define the inverseof (++) , which non-deterministically computes all splittings of a list, using a functional pattern in thefollowing way. (++) -1 :: [ a ] -> ([ a ] , [ a ])(++) -1 ( xs ++ ys ) = ( xs , ys ) Conversely, we can transform functional patterns into calls to the inverses of the functions used in thefunctional patterns. For the example of last this transformation looks as follows. last z = case (++) -1 z of( xs ,[ x ]) -> x Thus, if we had the inverse of any function at hand, we would be able to implement functional patternsdirectly without the need of a special primitive in the runtime system.
The goal of our research is to synthesize the inverse function for any given function in a lazy functional-logic programming language like Curry. More specifically, for any function f :: τ -> . . . -> τ n -> τ we want to obtain an inverse function f -1 :: τ -> ( τ , . . . , τ n ) that complies to the following specification. f -1 ( f x . . . x n ) = ( x , . . . , x n ) Note that we utilize functional patterns to specify the semantics of inverse functions. In particular, inversefunctions should exhibit the same non-strict behavior as functional patterns. As a result, the implemen-tation of (++) -1 from section 1 can serve as a specification implementation against which we can test asynthesized inverse function, e.g., by using equivalence checking of non-deterministic operations [9] asimplemented in CurryCheck [16].Another requirement for the synthesized inverse functions is that they should consist only of standardcode. Especially, they must not rely on functional patterns since we want to implement the latter usingthe synthesized inverse functions following the transformation scheme shown in section 1. Furthermore,using only standard code enables the application of established optimization techniques [8, 21].98 Implementing Functional Patterns bySynthesizing Inverse Functions Our approach to synthesize inverse functions is based on the idea to swap the sides of rules. We demon-strate this approach with the example of the synthesis of the inverse function (++) -1 .First, we recall the definition of (++) . (++) :: [ a ] -> [ a ] -> [ a ][] ++ ys = ys( x : xs ) ++ ys = x : ( xs ++ ys ) By swapping the sides of both rules we obtain the following preliminary inverse function. (++) -1 ys = ([] , ys )(++) -1 ( x :( xs ++ ys ) ) = ( x : xs , ys ) We can see that swapping rule sides results in new left-hand sides that may contain not only constructorsand variables but also function symbols. That is, we may get functional patterns on the new left-handsides. As stated in section 2, we must not use functional patterns within the definition of our synthesizedinverse function. But we can apply the transformation sketched in section 1 to eliminate the functionalpatterns. We then get the following definition, which is also the final inverse. (++) -1 ys = ([] , ys )(++) -1 ( x : z ) = case (++) -1 z of( xs , ys ) -> ( x : xs , ys ) Most notably, this definition complies with the specification for inverse functions from section 2.When implementing function inversion, non-linear right-hand sides can pose a challenge. As anexample, consider the following function that appends a list to itself. s e l f A p p e n d :: [ a ] -> [ a ]s e l f A p p e n d xs = xs ++ xs
If we swap the sides of a rule with a non-linear right-hand side, the resulting rule is non-left-linear whichis not allowed in traditional functional-logic programs. s e l f A p p e n d -1 :: [ a ] -> [ a ]s e l f A p p e n d -1 z = case (++) -1 z of( xs , xs ) -> xs The intended meaning of multiple occurrences of a variable on the left-hand side is usually that the actualarguments should be structurally equal [5]. Therefore, we can overcome the challenge of non-linear left-hand sides with the strict unification. s e l f A p p e n d -1 z = case (++) -1 z of( xs , ys ) | xs =:= ys -> xs Another common challenge in inverse computation is the handling of variables which are not referredto on right-hand sides. In the following definition, x is such an extra variable. tail :: [ a ] -> [ a ]tail ( x : xs ) = xs Since extra variables can in principle take any value of their type when the function is called, the inversefunction must return any possible value of the same type. For this we can easily use free variables. tail -1 :: [ a ] -> [ a ]tail -1 xs = x : xs where x free .Teegen 299 The approach presented so far is a first step, but there are some unresolved issues. Consider the followingprogram that is a variant of an example that was also discussed in the context of functional patterns [6]. g :: Int -> ( Int , Int ) f :: Int -> Intg 0 = ( f j , j ) where j free f i = 0
Most significantly, the program contains both extra variables ( i in the definition of f ) and non-linearright-hand sides (there are two occurrences of j in the definition of g ). For this example, we are interestedin the inverse of g . If we apply the first step of our transformation scheme to g , i.e., we swap the rulesides, we get the following inverse. g -1 :: ( Int , Int ) -> Intg -1 ( f j , j ) = 0 According to the semantics of functional patterns [6], this definition of g -1 should be equivalent to thefollowing one. g -1 (0 , j ) = 0 It is also the inverse of the following simpler definition of g , which is equivalent to the one above. g 0 = (0 , j ) where j free Normally, we would expect the inverses of two equivalent functions to also be equivalent. This is,however, not the case for the two variants of g -1 . Further applying our transformation scheme to the firstvariant of g -1 leads to the following definitions. g -1 (z , x ) = case f -1 z of f -1 :: Int -> Inty | x =:= y -> 0 f -1 Now consider the expression g -1 (0, failed) . While the inverse function obtained from the simplerdefinition of g correctly computes the value for that expression, evaluating it fails with the more com-plex variant of g -1 due to the strict unification. The problem arises solely from the aforementionedcombination of extra variables and non-linearity in the program. Unfortunately, we do not yet have asolution for this problem. In this particular case, we could analyze the program, but in general it is un-decidable at compile-time whether a variable is used or not at run-time. Note that both properties alonedo not pose a problem, only their combination does.Another open issue is that we have not yet tackled the inversion of higher-order functions, a notoriousdifficult problem. For instance, consider the infix application operator ($) , which is defined as follows. ( $ ) :: a -> ( a -> b ) -> bx $ f = f x Its inverse ( $ ) -1 :: b -> (a , a -> b ) would have to return all value-function-pairs so that the function applied to the value would yield theinverse’s input. Since we cannot easily guess arbitrary functions, it seems reasonable to restrict ourapproach to first-order programs. In fact, functional patterns are affected by this problem as well, so thesame restriction would make sense for them too. One possible (and well-integrated) way to do this is tointroduce additional type class constraints [17].00 Implementing Functional Patterns bySynthesizing Inverse Functions There is earlier work on function inversion in the field of declarative programming. Abramov and Glckpresented an algorithm for inverse interpretation in a first-order functional programming language re-stricted to tail-recursion [1, 2]. The so-called Universal Resolving Algorithm (URA) is based on thenotion of perfect process trees [14]. Abramov, Glck and Klimov later extended the original URA togeneral recursion and improved efficiency as well as termination by reducing the search space [3]. Thereduction of the search space was achieved by inspecting partially produced output, i.e., utilizing thelazy evaluation of functional programming languages. Furthermore, they discussed the introduction ofan mgu-based equality operator that is very similar to the non-strict unification operator proposed earlierin [6]. Another independent extension of the original URA was presented by Secher and Sørensen. Theyimproved the termination of the URA, but at the cost of its soundness [22].Glck and Kawabe presented an approach to automatically derive the inverse of first-order functions[13]. In the process, they used methods of LR parsing to eliminate non-determinism resulting from theautomatic program inversion. Since they aimed to obtain deterministic inverse programs in the end, theyrestricted themselves to injective functions in the first place.Total inversion is subsumed by partial inversion where a part of the inputs may be fixed. The partialinverse of a function then computes the remaining inputs with respect to the fixed ones and a given output.Nishida, Sakai and Sakabe proposed a partial-inversion compiler of constructor term rewriting systemsthat first generates a conditional term rewriting system and then unravels it to an unconditional system[20]. Their approach handles extra variables by applying narrowing strategies, but only covers strictprogramming languages which again easily allows for non-linear right-hand sides. Almendros-Jimnezand Vidal described another partial inversion technique—again for first-order functional programs—thatis specialized on inductively sequential functions [4]. In the same paper, they also sketched extensionsof their method to higher-order and laziness.While most related work provide algorithms for (partial) function inversion, few publications coverthe semantics and desired properties of function inversion. For example, Brael and Christiansen de-scribed a relational semantics for first-order-restricted Curry and also covered properties of functioninversion in a lazy functional-logic programming language [10].
We have presented our research on implementing functional patterns by deriving the inverse of a functionfrom its original definition in the lazy functional-logic programming language Curry. In essence, our ideais to swap the sides of rules and to subsequently eliminate all resulting functional patterns. Because oursynthesis generates only standard code, the resulting inverses can be subject to further optimizations.So far our approach is limited to first-order functions whose rules do not involve both extra variablesand non-linear right-hand sides. However, we think that we already cover many relevant functions thisway. For all remaining cases we can temporarily resort to the specification implementation based onfunctional patterns. The consequence is that the runtime system still has to provide the non-strict unifi-cation primitive for the time being. With methods of static analysis we could shift the boundary, but theproblem itself remains undecidable and, thus, would require additional effort at run-time. Nonetheless,we think that being able to directly invert a relevant class of functions is a good starting point for futureresearch..Teegen 301
References [1] Sergei Abramov & Robert Gl¨uck (2000):
The Universal Resolving Algorithm: Inverse Computation in aFunctional Language . In: MathematicsofProgramConstruction, MPC’00, Springer, Berlin, Heidelberg, pp.187–212, doi: .[2] Sergei Abramov & Robert Gl¨uck (2002):
The Universal Resolving Algorithm and its Correctness: InverseComputation in a Functional Language . Science of Computer Programming 43(2), pp. 193–229, doi: .[3] Sergei Abramov, Robert Gl¨uck & Yuri Klimov (2007):
An Universal Resolving Algorithm for Inverse Com-putation of Lazy Languages . In: Proceedings of the 6th InternationalAndrei Ershov Memorial Conferenceon Perspectives of Systems Informatics, PSI’06, Springer, Berlin, Heidelberg, pp. 27–40, doi: .[4] Jes´us M. Almendros-Jim´enez & Germ´an Vidal (2007):
Automatic Partial Inversion of Inductively SequentialFunctions . In: Proceedingsofthe18thInternationalConferenceonImplementationandApplicationofFunc-tionalLanguages, IFL’06, Springer, Berlin, Heidelberg, pp. 253–270, doi: .[5] Sergio Antoy (2001):
Constructor-Based Conditional Narrowing . In: Proceedings of the 3rd ACM SIG-PLAN InternationalConferenceon Principlesand Practiceof DeclarativeProgramming, PPDP01, Associa-tion for Computing Machinery, New York, NY, USA, pp. 199–206, doi: .[6] Sergio Antoy & Michael Hanus (2006):
Declarative Programming with Function Patterns . In: Proceedingsof the 15th InternationalConferenceon Logic-Based Program Synthesis and Transformation, LOPSTR’05,Springer, Berlin, Heidelberg, pp. 6–22, doi: .[7] Sergio Antoy & Michael Hanus (2010):
Functional Logic Programming . Communications of the ACM53(4), pp. 74–85, doi: .[8] Sergio Antoy & Michael Hanus (2017):
Eliminating Irrelevant Non-determinism in Functional Logic Pro-grams . In: PracticalAspectsofDeclarativeLanguages, PADL’17, Springer, Cham, pp. 1–18, doi: .[9] Sergio Antoy & Michael Hanus (2018):
Equivalence Checking of Non-deterministic Operations . In: Pro-ceedingsofthe14thInternationalSymposiumonFunctionalandLogicProgramming, FLOPS’18, Springer,Cham, pp. 149–165, doi: .[10] Bernd Braßel & Jan Christiansen (2008):
A Relation Algebraic Semantics for a Lazy Functional Logic Lan-guage . In: Relations and Kleene Algebrain ComputerScience, RelMiCS’08, Springer, Berlin, Heidelberg,pp. 37–53, doi: .[11] Jan Christiansen & Sebastian Fischer (2008):
EasyCheck – Test Data for Free . In: Proceedings of the 9thInternationalSymposium on Functionaland Logic Programming, FLOPS’08, Springer, Berlin, Heidelberg,pp. 322–336, doi: .[12] Sandra Dylus, Jan Christiansen & Finn Teegen (2020):
Implementing a Library for Probabilistic Program-ming Using Non-strict Non-determinism . Theory and Practice of Logic Programming 20(1), pp. 147–175,doi: .[13] Robert Gl¨uck & Masahiko Kawabe (2004):
Derivation of Deterministic Inverse Programs Based on LRParsing . In: Functional and Logic Programming, FLOPS’04, Springer, Berlin, Heidelberg, pp. 291–306,doi: .[14] Robert Gl¨uck & Andrei V. Klimov (1993):
Occam’s Razor in Metacomputation: the Notion of a PerfectProcess Tree . In Patrick Cousot, Moreno Falaschi, Gilberto Fil´e & Antoine Rauzy, editors: StaticAnalysis,Springer, Berlin, Heidelberg, pp. 112–123, doi: .[15] Juan Carlos Gonz´alez-Moreno, M.T. Hortal´a-Gonz´alez, F.J. L´opez-Fraguas & M. Rodr´ıguez-Artalejo (1999):
An approach to declarative programming based on a rewriting logic . JournalofLogicProgramming40, pp.47–87, doi: .
02 Implementing Functional Patterns bySynthesizing Inverse Functions [16] Michael Hanus (2017):
CurryCheck: Checking Properties of Curry Programs . In: Proceedingsof the 26thInternational Symposium on Logic-Based Program Synthesis and Transformation, LOPSTR’16, Springer,Cham, pp. 222–239, doi: .[17] Michael Hanus & Finn Teegen (2020):
Adding Data to Curry . In: DeclarativeProgrammingandKnowledgeManagement, Springer, Cham, pp. 230–246, doi: .[18] Michael Hanus (ed.) (2016):
Curry: An Integrated Functional Logic Language (Vers. 0.9.0) . Available at .[19] Francisco L´opez-Fraguas & Jaime S´anchez-Hern´andez (1999):
TOY: A Multiparadigm Declarative System .In: Rewriting Techniques and Applications, RTA’99, Springer, Berlin, Heidelberg, pp. 244–247, doi: .[20] Naoki Nishida, Masahiko Sakai & Toshiki Sakabe (2005):
Partial Inversion of Constructor Term RewritingSystems . In: Proceedingsofthe16thInternationalConferenceonTermRewritingandApplications, RTA’05,Springer, Berlin, Heidelberg, pp. 264–278, doi: .[21] Bj¨orn Peem¨oller (2016):
Normalization and Partial Evaluation of Functional Logic Programs . Ph.D. thesis,Kiel University. Available at https://macau.uni-kiel.de/receive/diss_mods_00021156 .[22] Jens Peter Secher & Morten Heine Sørensen (2002):
From Checking to Inference via Driving and DagGrammars . In: Proceedings of the 2002 ACM SIGPLAN Workshop on Partial Evaluation and Semantics-based Program Manipulation, PEPM’02, ACM, New York, NY, USA, pp. 41–51, doi:10.1145/509799.503036