Zulip Chat Archive

Stream: new members

Topic: Beautify code


Pedro Minicz (Oct 10 2020 at 19:23):

Any suggestions in how to make the following prettier?

@[derive decidable_eq]
inductive term : Type
| var :   term
| impl : term  term  term

open term

def {u} select {α : Type u} : list α  list (α × list α)
| (a :: as) := (a, as) :: list.map (bifunctor.snd (list.cons a)) (select as)
| [] := []

meta def ljt : list term  term  list term
| env t@(var x) := if t  env then return t else do
  (impl a b, env)  select env | alternative.failure,
  match a with
  | (var x) := do
    guard $ var x  env,
    ljt (b :: env) t
  | (impl c d) := do
    ljt (impl d b :: env) (impl c d),
    ljt (b :: env) t
  end
| env t@(impl a b) := if t  env then return t else ljt (a :: env) b

Pedro Minicz (Oct 10 2020 at 19:24):

I would like to make it more like the Haskell version:

import Control.Monad
import Data.Bifunctor

data Term
    = Var Char
    | Impl Term Term
    deriving (Eq, Show)

ljt :: [Term] -> Term -> [Term]
ljt env t
    | elem t env = return t
ljt env (Impl a b) = ljt (a : env) b
ljt env t = do
    (Impl a b, env) <- select env
    case a of
        Var a -> do
            guard $ elem (Var a) env
            ljt (b : env) t
        Impl c d -> do
            ljt (Impl d b : env) (Impl c d)
            ljt (b : env) t

select :: [a] -> [(a, [a])]
select (x:xs) = (x, xs) : map (second (x:)) (select xs)
select []     = []

Pedro Minicz (Oct 10 2020 at 19:25):

It particularly bothers me that if t ∈ env then return t else ... gets written twice in the Lean version.

Pedro Minicz (Oct 10 2020 at 19:26):

(Also, if anyone has any clue on how to convince Lean that ljt terminates, any help would be highly appreciated. I got it from here)

Kyle Miller (Oct 10 2020 at 20:24):

Here's a hack using optional parameters, but it's uglier in another axis:

import control.bifunctor

@[derive decidable_eq]
inductive term : Type
| var :   term
| impl : term  term  term

open term

def {u} select {α : Type u} : list α  list (α × list α)
| (a :: as) := (a, as) :: list.map (bifunctor.snd (list.cons a)) (select as)
| [] := []

meta def ljt : Π (env : list term) (t : term) (in_env : opt_param bool (t  env)), list term
| env t tt := return t
| env t@(impl a b) ff := ljt (a :: env) b
| env t@(var x) ff := do
  (impl a b, env)  select env | [],
  match a with
  | (var x) := do guard $ var x  env,
                  ljt (b :: env) t
  | (impl c d) := do
    ljt (impl d b :: env) (impl c d),
    ljt (b :: env) t
  end

Pedro Minicz (Oct 10 2020 at 20:49):

Wow. I would have never though of that.

Pedro Minicz (Oct 10 2020 at 20:50):

Is opt_param used somewhere other than in tactics?

Mario Carneiro (Oct 11 2020 at 02:04):

It is used in structures to give default values for some arguments, e.g. lt in src#preorder

Mario Carneiro (Oct 11 2020 at 02:10):

Assuming you don't care about termination, you can get closer to the haskell version by just doing this:

meta def ljt : list term  term  list term
| env t := if t  env then return t else
  match t with
  | impl a b := ljt (a :: env) b
  | _ := do
    (impl a b, env)  select env | failure,
    match a with
    | (var x) := do
      guard $ var x  env,
      ljt (b :: env) t
    | (impl c d) := do
      ljt (impl d b :: env) (impl c d),
      ljt (b :: env) t
    end
  end

Pedro Minicz (Oct 11 2020 at 18:21):

Thanks!

Pedro Minicz (Oct 11 2020 at 18:21):

I do care about termination. But the either versions look as far from being easy to convince Lean it terminates as it can be.

Mario Carneiro (Oct 11 2020 at 19:00):

I thought about it for a while and the termination condition is incredibly complex

Mario Carneiro (Oct 11 2020 at 19:00):

do you have an informal proof of termination?

Bhavik Mehta (Oct 11 2020 at 21:06):

I've got an informal proof but I think to convince lean it might take quite a bit of refactoring of ljt, (for instance knowing that the env after the select is the original one minus an element), but I think the maths here is mostly straightforward (plus some standard results about wellorderings which I don't believe are in mathlib)

Kyle Miller (Oct 11 2020 at 21:30):

@Pedro Minicz By the way, it doesn't seem you've translated the Prolog from the paper completely faithfully. The cut operator ! means to throw out all backtracking for the most recent choice point, and I'm not sure you can do that within the list monad.

This might be a more faithful rendition of the ljb algorithm (with no guarantees it's correct :smile:):

import control.bifunctor

@[derive decidable_eq]
inductive term : Type
| var :   term
| impl : term  term  term

open term

def {u} select {α : Type u} : list α  list (α × list α)
| (a :: as) := (a, as) :: list.map (bifunctor.snd (list.cons a)) (select as)
| [] := []

meta def ljb : list term  term  bool
| env t@(impl a b) := if t  env then tt else ljb (a :: env) b
| env t@(var x) := if t  env then tt else
  let imps : list (list term) := do
    (impl a b, env2)  select env | [],
    match a with
    | (impl c d) := guard $ ljb (impl d b :: env2) a
    | _ := guard $ a  env2
    end,
    return (b :: env2)
  in match imps with
     | [] := ff
     | (env2 :: _) := ljb env2 t
     end

Pedro Minicz (Oct 11 2020 at 22:20):

It has been a while since I've written the Haskell code, but I am pretty sure ! in the Prolog only exists for performance reasons. But again, that was a while ago, so I may be miss remembering it.

Pedro Minicz (Oct 11 2020 at 22:22):

Mario Carneiro said:

do you have an informal proof of termination?

Only a half-baked one.

Pedro Minicz (Oct 11 2020 at 22:25):

I believe the amount of impls in env + t can only get smaller or stay the same. I feel like working with finsets could make this clearer (or prove me wrong).

Pedro Minicz (Oct 11 2020 at 22:25):

Bhavik Mehta said:

I've got an informal proof but I think to convince lean it might take quite a bit of refactoring of ljt, (for instance knowing that the env after the select is the original one minus an element), but I think the maths here is mostly straightforward (plus some standard results about well founded relations which I don't believe are in mathlib)

I'd very interested in seeing it.

Bhavik Mehta (Oct 11 2020 at 22:25):

Pedro Minicz said:

Bhavik Mehta said:

I've got an informal proof but I think to convince lean it might take quite a bit of refactoring of ljt, (for instance knowing that the env after the select is the original one minus an element), but I think the maths here is mostly straightforward (plus some standard results about well founded relations which I don't believe are in mathlib)

I'd very interested in seeing it.

Working on the lean version right now :)

Pedro Minicz (Oct 11 2020 at 22:25):

Is finset a monad? How hard would it be turn it into one?

Kyle Miller (Oct 11 2020 at 22:26):

The cut operator is sometimes necessary. I don't think it's necessary here for correctness, but it seems like the run time would be horrendous without it.

Pedro Minicz (Oct 11 2020 at 22:26):

One of the saddest things about Haskell is that there is no (simple) Set monad.

Kyle Miller (Oct 11 2020 at 22:27):

The only real change I made was to wrap up some of the select and sub-ljb code into its own list monad, then truncate it.

Kyle Miller (Oct 11 2020 at 22:28):

Pedro Minicz said:

Is finset a monad? How hard would it be turn it into one?

I think it already is one -- at least, there's finset.bind if I remember correctly.

Kyle Miller (Oct 11 2020 at 22:29):

Actually, according to the docs there isn't a monad instance. But finset.bind is indeed the right bind operator.

Pedro Minicz (Oct 11 2020 at 22:30):

Yes, there is one! Wow, I didn't find it while looking for it yesterday. There doesn't seem to be a monad instance (although, it may not be possible since finset.bind : finset α → (α → finset β) → finset β needs [decidable_eq β]).

Pedro Minicz (Oct 11 2020 at 22:31):

Maybe there is some hack-y way to get do notation...

Pedro Minicz (Oct 11 2020 at 22:53):

A termination proof can be found here: https://www.cs.cmu.edu/~fp/courses/atp/cmuonly/D92.pdf (also https://research-repository.st-andrews.ac.uk/bitstream/handle/10023/16793/Dyckhoff_2018_contraction_free_JSL_1680.pdf)

Bhavik Mehta (Oct 11 2020 at 22:56):

alright I've got a working proof, minus the part which in that is referenced to [4], showing that the multiset ordering is wellfounded if the underlying partial order is

Bhavik Mehta (Oct 11 2020 at 22:58):

https://gist.github.com/b-mehta/ee89376db987b749bd5120a2180ce3df

Bhavik Mehta (Oct 11 2020 at 22:58):

it's pretty messy, I made a similar transformation to what Kyle did, but using Prop instead of bool

Bhavik Mehta (Oct 11 2020 at 22:58):

I don't think this version will compute at all well, but it might give you a better idea of how to prove termination

Bhavik Mehta (Oct 11 2020 at 22:59):

the two main points are to use the dershowitz-manna ordering, and that in the recursive steps you need to have access to the properties of the sub-formulae chosen (which match or select doesn't give you automatically)

Mario Carneiro (Oct 12 2020 at 05:04):

If you use Prop, it's pretty trivial to write this without a termination proof, since you can just use an inductive type

Mario Carneiro (Oct 12 2020 at 05:04):

it's probably clearer that way too

Mario Carneiro (Oct 12 2020 at 05:05):

This is a great example where it would be better to write the code first and the termination proof later

Mario Carneiro (Oct 12 2020 at 05:06):

it might be interesting to try applying that general recursion thing that Simon was working on (or using roption directly)

Mario Carneiro (Oct 12 2020 at 05:22):

yay:

import control.bifunctor control.fix

@[derive decidable_eq]
inductive term : Type
| var :   term
| impl : term  term  term

open term

def {u} select {α : Type u} : list α  list (α × list α)
| (a :: as) := (a, as) :: list.map (bifunctor.snd (list.cons a)) (select as)
| [] := []

def ljtF (ljt : list term  term  roption (list term)) :
  list term  term  roption (list term)
| env t := if t  env then pure [t] else
  match t with
  | impl a b := ljt (a :: env) b
  | _ := list.join <$> (select env).mmap (λ te,
    match te with
    | (impl (var x) b, env) :=
      if var x  env then
        ljt (b :: env) t
      else pure []
    | (impl (impl c d) b, env) :=
      (++) <$> ljt (impl d b :: env) (impl c d)
           <*> ljt (b :: env) t
    | _ := pure []
    end)
  end

def ljt (env : list term) (t : term) : roption (list term) :=
@roption.fix (list term × term) (λ _, list term)
  (λ IH env, t⟩, ljtF (λ env t, IH (env, t)) env t) (env, t)

Mario Carneiro (Oct 12 2020 at 05:23):

the monads really don't play well together though


Last updated: Dec 20 2023 at 11:08 UTC