
Ask HN: Reversible function definitions - posterboy
What do computer scientists call a function that allows inference of transformations between two data types from a single definition, and does any language support this and explicit calls or implicit conversions?<p><pre><code>   function convert
   (rad_t) phi = pi&#x2F;180 * (deg_t) phi
   rad_t alpha = convert((deg_t) 45)  &#x2F;&#x2F;explicit
   deg_t beta = pi&#x2F;4 + alpha          &#x2F;&#x2F;implicit
</code></pre>
rad_t and deg_t might be together in a typeclass <i>archs</i> or sumsuch.<p>disc.: I can&#x27;t reach stackoverflow, so I&#x27;m posting here.
======
bjourne
There are Python packages that do what you want (units and numericalunits),
but they aren't used much presumably due to the overhead they bring. You might
also be interested in Prolog which feature what is, essentially, invertible
functions. E.g:

convert(Deg, Rad) :- nonvar(Deg), Rad is Deg * pi / 180\. convert(Deg, Rad) :-
nonvar(Rad), Deg is Rad * 180 / pi.

In this case, it doesn't work well because of floating point math, but it
works well for functions manipulating lists. The Factor language also feature
function inversion in an addon: [http://docs.factorcode.org/content/article-
inverse%2Cintro.h...](http://docs.factorcode.org/content/article-
inverse%2Cintro.html)

Afaik, figuring out what functions in general are invertible is an unsolved
problem in computer science.

------
miguelrochefort
They're call _isomorphic functions_.

Recently, I've been thinking a lot about them and also have been looking for a
language/framework that supports them. What would be very interesting is a way
to inverse non-isomorphic functions and get a constrained set of possible
values (i.e., the reverse of age(birth, now) would return a range of date for
either birth or now). Often, the complete value is not necessary and you're
only interested in some subset of it (i.e., that a person's age is greater
than 18).

~~~
zmonx
It is true: An isomorphic function _admits_ an inverse _function_.

However, what you are describing in the second paragraph is a generalization
of functions: You are now reasoning in terms of _relations_ , where a single
entity can be related to _one or more_ others. Hence, a programming language
that reasons in terms of _relations_ (not functions) seems very appropriate
for your use case. Logic programming is a such a natural generalization of
functional programming.

For example, if you want to reason flexibly over dates and times, check out
Prolog and its finite domain _constraints_.

Michael Hendricks has implemented a great library for this, called
library(julian):

[http://mndrix.github.io/julian/](http://mndrix.github.io/julian/)

This library lets you reason in terms of ranges and other constraints to
describe general _relations_ between dates and times.

------
zmonx
There is a natural generalization of functions, called _relations_. To benefit
from relations, have a look at a _logic programming_ language such as Prolog.
In Prolog, relations are defined by _predicates_ and constitute the basic
building blocks of all Prolog programs.

A predicate is indeed sometimes called "reversible" if it can also be used in
a direction that seems somewhat unexpected at first. For example, the
predicate length/2 can be used to _both_ compute the length of a list and
_generate_ a list of a _given_ length.

However, a predicate should ideally not only be "reversible", but indeed
_completely general_. This means that we should also be able to ask: Which
solutions are there _at all_? This is a query where _nothing_ is given, and
the Prolog system must find solutions on its own. Indeed, length/2 also
satisfies this criterion. We can ask for example:

    
    
       ?- length(Ls, L).
       Ls = [],
       L = 0 ;
       Ls = [_2168],
       L = 1 ;
       Ls = [_2168, _2174],
       L = 2 ;
       etc.
    

and the Prolog system _generates_ as many lists (and their lengths) as we
want.

So, calling such predicates "reversible" still does not do justice to what
they actually are, namely completely general _relations_ that work in _all_
directions. Therefore, when programming in Prolog, do not fall into the trap
of calling your predicates "reversible" or "usable in _the_ other direction"
is if there were only _one_ such other direction. Instead, think in terms of
_relations_ between entities, and use language features that allow such
general reasoning.

For instance, let us consider your concrete example. Using Prolog and its
CLP(R) _constraints_ , we can very generally describe the _relation_ between
floating point numbers. CLP(R) is for example available in SICStus Prolog and
also a few other systems:

    
    
        :- use_module(library(clpr)).
    
        pi(Pi) :- Pi is 4*atan(1).
    
        rad_deg(Rad, Deg) :-
                pi(Pi),
                { Rad = Pi / 180 * Deg }.
    

Now the point: rad_deg/2 is a general _relation_ and can be used in _all_
directions.

For example, it can be used to compute the first argument if only the second
is known:

    
    
      ?- rad_deg(Alpha, 45).
      Alpha = 0.7853981633974483 .
    

Conversely, it can be used to compute the second argument if only the first is
known:

    
    
      ?- rad_deg(0.7853981633974483, Beta).
      Beta = 45.0 .
    

Here is your slightly more complex example, which is also an instance of
computing the _second_ argument:

    
    
       ?- pi(Pi),
          rad_deg(Alpha0, 45),
          { Alpha = Pi/4 + Alpha0 },
          rad_deg(Alpha, Beta).
       ...,
       Beta = 90.0 .
    

But it _does not stop_ there! The relation can also be used _in the most
general way_ to answer: Which solutions are there _at all_?

    
    
      ?- rad_deg(R, D).
      {D=57.29577951308232*R}.
    

In this case, it provides an _answer_ that expresses the _general relation_
between the two arguments even though none of them are known.

And it _still_ doesn't stop there! The relation can also be used to ask: Does
the relation hold between two _given_ numbers?

    
    
      ?- rad_deg(0, 0).
      true.
    

If the relation _doesn 't_ hold between two given floating point numbers, the
system tells us:

    
    
      ?- rad_deg(0, 1).
      false.
    

So, this is a true _relation_ between floating point numbers, usable in _all_
directions!

Caveat: Floating point numbers are an extremely bad way to represent numbers,
and I can only advice to use better representations instead. For example, Unum
computing looks very promising. In Prolog, consider using _rational_ numbers,
available via CLP(Q). Declarative reasoning over _integers_ is available via
CLP(FD) constraints. All these systems let you implement general _relations_
between numeric expressions.

~~~
miguelrochefort
This is awesome. Why aren't we all programming this way?

~~~
PaulHoule
It's an interesting question.

In the 1980s, there was a lot of interest in Prolog, particularly people
thought it would be good for massively parallel programming since you don't
have to specify the exact sequence things happen in.

It was discovered pretty early that Prolog does not parallelize well, so that
research went in another direction towards languages that were less expressive
such as KL1.

I think Prolog has not really caught on because the combination of imperative
in logic programming is awkward. For instance, you might implement a loop that
uses logical failure to backtrack (and run the next iteration of the loop)
when you have what you think of as a true condition.

Since then there has been a lot of interest in Datalog which is not a
precisely-defined language with a particular syntax, but rather the pure
logical core of Prolog which can be implemented in languages like Clojure or
the SPARQL-RDF ecosystem, etc.

On the other hand there are also "constraint programming" languages which are
more expressive than Prolog as well as SAT/SMT solvers and other tools that
use more complex solving and optimization strategies.

~~~
zmonx
With "constraint programming" languages, do you mean languages such as
MiniZinc that provide dedicated syntax for some tasks?

I am asking because support for constraint programming is among the most
important and most distinguishing features of common Prolog systems: All
widely used Prolog systems (GNU-Prolog, SICStus etc.) ship _at least_ with a
constraint solver over _integers_ , and often with many more, such as
constraints over Herbrand terms (dif/2), constraints over rational numbers and
also constraints over Boolean variables (SAT constraints).

So, support for constraint programming is typically provided also natively by
Prolog systems, with a simple and also very expressive syntax that blends in
seamlessly into Prolog like any other relation.

There was a recent HN discussion about why Prolog is not yet more popular:

[https://news.ycombinator.com/item?id=14439137](https://news.ycombinator.com/item?id=14439137)

In my view, Prolog implementations are now becoming interesting because they
are now quite robust, feature-rich and also reasonably efficient.

~~~
miguelrochefort
I actually started that thread about Prolog :)

Recently, I've been looking at Mercury and Eve. I really thing logic
programming is underrated.

