orcmid / miser

The Miser Project is practical demonstration of computation-theoretical aspects of software
https://orcmid.github.io/miser/
Apache License 2.0
3 stars 1 forks source link

"Textual" substitution fails? #10

Closed rnd0101 closed 6 years ago

rnd0101 commented 6 years ago

In the following plain .D is not equivalent to .A .D .B, even though .A .D .B evaluates to .D:

oMiser> (.D "x" "x") ("eq" "neq")
INPUT: ((.D :: ("x" :: "x")) :: ("eq" :: "neq"))
ev (.SELF) (.ARG) ("x") -> "x"
ev (.SELF) (.ARG) ("x") -> "x"
ev (.SELF) (.ARG) ((.D :: ("x" :: "x"))) -> .A
ev (.SELF) (.ARG) ("eq") -> "eq"
ev (.SELF) (.ARG) ("neq") -> "neq"
ap ("eq") ("neq") -> ("eq" :: "neq")
ev (.SELF) (.ARG) (("eq" :: "neq")) -> ("eq" :: "neq")
ap (.A) (("eq" :: "neq")) -> "eq"
ev (.SELF) (.ARG) (((.D :: ("x" :: "x")) :: ("eq" :: "neq"))) -> "eq"

OUTPUT: "eq"

oMiser> .A .D .B
INPUT: (.A :: (.D :: .B))
ev (.SELF) (.ARG) (.A) -> .A
ev (.SELF) (.ARG) (.D) -> .D
ev (.SELF) (.ARG) (.B) -> .B
ap (.D) (.B) -> (.D :: (`(.B) :: .ARG))
ev (.SELF) (.ARG) ((.D :: .B)) -> (.D :: (`(.B) :: .ARG))
ap (.A) ((.D :: (`(.B) :: .ARG))) -> .D
ev (.SELF) (.ARG) ((.A :: (.D :: .B))) -> .D

OUTPUT: .D

oMiser> ((.A .D .B) "x" "x") ("eq" "neq")
INPUT: (((.A :: (.D :: .B)) :: ("x" :: "x")) :: ("eq" :: "neq"))
ev (.SELF) (.ARG) (.A) -> .A
ev (.SELF) (.ARG) (.D) -> .D
ev (.SELF) (.ARG) (.B) -> .B
ap (.D) (.B) -> (.D :: (`(.B) :: .ARG))
ev (.SELF) (.ARG) ((.D :: .B)) -> (.D :: (`(.B) :: .ARG))
ap (.A) ((.D :: (`(.B) :: .ARG))) -> .D
ev (.SELF) (.ARG) ((.A :: (.D :: .B))) -> .D
ev (.SELF) (.ARG) ("x") -> "x"
ev (.SELF) (.ARG) ("x") -> "x"
ap ("x") ("x") -> ("x" :: "x")
ev (.SELF) (.ARG) (("x" :: "x")) -> ("x" :: "x")
ap (.D) (("x" :: "x")) -> (.D :: (`(("x" :: "x")) :: .ARG))
ev (.SELF) (.ARG) (((.A :: (.D :: .B)) :: ("x" :: "x"))) -> (.D :: (`(("x" :: "x")) :: .ARG))
ev (.SELF) (.ARG) ("eq") -> "eq"
ev (.SELF) (.ARG) ("neq") -> "neq"
ap ("eq") ("neq") -> ("eq" :: "neq")
ev (.SELF) (.ARG) (("eq" :: "neq")) -> ("eq" :: "neq")
ev ((.D :: (`(("x" :: "x")) :: .ARG))) (("eq" :: "neq")) (`(("x" :: "x"))) -> ("x" :: "x")
ev ((.D :: (`(("x" :: "x")) :: .ARG))) (("eq" :: "neq")) (.ARG) -> ("eq" :: "neq")
ev ((.D :: (`(("x" :: "x")) :: .ARG))) (("eq" :: "neq")) ((.D :: (`(("x" :: "x")) :: .ARG))) -> .B
ap ((.D :: (`(("x" :: "x")) :: .ARG))) (("eq" :: "neq")) -> .B
ev (.SELF) (.ARG) ((((.A :: (.D :: .B)) :: ("x" :: "x")) :: ("eq" :: "neq"))) -> .B

OUTPUT: .B

Here is one answer for this:

https://github.com/orcmid/miser/issues/7#issuecomment-357468310 but discussion moved here.

The proposed addition of enclosures ((.A .D .B) ‵("x" ‵("y" "z")) ‵("x" ("y" "z"))) ("eq" "neq") does not solve the problem: result is still .B.

Construct like, again, when evaluated alone:

.A `(.A .D)

seems to handle all possible situations with .A and .B and .D.

But when it textually substitutes .D in the larger expression, .D stops to work.

oMiser> (.A `(.A .D)) `("x" "y")
INPUT: ((.A :: `((.A :: .D))) :: `(("x" :: "y")))
ev (.SELF) (.ARG) (.A) -> .A
ev (.SELF) (.ARG) (`((.A :: .D))) -> (.A :: .D)
ap (.A) ((.A :: .D)) -> .A
ev (.SELF) (.ARG) ((.A :: `((.A :: .D)))) -> .A
ev (.SELF) (.ARG) (`(("x" :: "y"))) -> ("x" :: "y")
ap (.A) (("x" :: "y")) -> "x"
ev (.SELF) (.ARG) (((.A :: `((.A :: .D))) :: `(("x" :: "y")))) -> "x"

OUTPUT: "x"

oMiser> (.B `(.A .D)) `("x" "y")
INPUT: ((.B :: `((.A :: .D))) :: `(("x" :: "y")))
ev (.SELF) (.ARG) (.B) -> .B
ev (.SELF) (.ARG) (`((.A :: .D))) -> (.A :: .D)
ap (.B) ((.A :: .D)) -> .D
ev (.SELF) (.ARG) ((.B :: `((.A :: .D)))) -> .D
ev (.SELF) (.ARG) (`(("x" :: "y"))) -> ("x" :: "y")
ap (.D) (("x" :: "y")) -> (.D :: (`(("x" :: "y")) :: .ARG))
ev (.SELF) (.ARG) (((.B :: `((.A :: .D))) :: `(("x" :: "y")))) -> (.D :: (`(("x" :: "y")) :: .ARG))

OUTPUT: (.D :: (`(("x" :: "y")) :: .ARG))

I am not sure if it's implementation or theory issue. Or maybe it's not the way miser supposed to perform evaluations, but how can I then apply applicative part (the .A .D. .B) to the data? And how to compose more complex applicative expressions?

So far my understanding was, that Miser evaluates a script (on the left) and applies to "data" on the right, but "data" can be script's "commands". What am I doing wrong?

I started those to figure out how can I evaluate combinators in Miser. That is, given S, K, I definitions, how to build something like (SK)(KS) and apply to the data / combinators. Can't find syntactical way for that.

orcmid commented 6 years ago

Question Closed

I misled myself and @rnd0101 in consequence. The straightforward handling of ob-exp, and the proper grammar for it, are all now resolved in Question #8, oFrugal Grammar?, and Question #14, Resolving oFrugal Applicative-Expression Grammar.

The following discussion attempts to clear the misunderstanding I created about a separate eval stage between read and print with the REPL. Formally, eval happens as part of the read ob-exp interpretation of the syntax. While there might be internal separation to avoid actual computations until after the parse is successful, the formal specification is more direct when presented as if the computations occur with the parsing. A sophisticated parser would do better to ensure the complete input is well-formed before carrying out the specified constructions.

The discussion is here for historical purposes. The conclusions are reflected directly in the #8 and #14 questions.

Oh, Oh

@rnd0101 So far my understanding was, that Miser evaluates a script (on the left) and applies to "data" on the right, but "data" can be script's "commands". What am I doing wrong?

I think the problem is assuming that some sort of text substitution is involved in the rules for applicative-expression evaluation and for applicative interpretation.

Checking Combinators

I started those to figure out how can I evaluate combinators in Miser. That is, given S, K, I definitions, how to build something like (SK)(KS) and apply to the data / combinators. Can't find syntactical way for that.

Supposing you have cS and cK, expressed as canonical obs such that

then it works to construct a combinator-representing ob via

so then

The way to express these two stages are

Here There Be Dragons

The oFrugalese REPL suggestion (from Question #7 comment) would look something like

> ob cS = .C :: ‵.C :: (.C :: (.E :: (.C :: (.E :: .ARG) :: ‵.ARG))            
                         :: ‵(.C :: (.E :: .ARG) :: ‵.ARG) ) ;
> ob cK = .E :: .ARG ;
> ob cSKcKS = @cS @cK ;
> @cSKcKS X;
X

and here might be the problem. There should be no eval in the cS and cK ob statements, just ob construction. Eval would fail if carried out on given ob as expressed since those obs are designed to be used, as written, as ap operator scripts. This means that application is not signified by :: for the above to work. (Apology: I must stop saying that that eval is used on the ob that is expressed. It might be used, but it yields the ob as expressed from an intermediate form created for that purpose. I shouldn't be muddying the waters with that, especially since I led myself astray with such carelessness.)

The REPL expression and its computation of an ob needs to be a bit more elaborate than running eval on the whole thing. The evaluation in the two final statements use applicative notation, not because of the @ but because of the juxtaposition and those applications are carried out via an eval of that part (there the only parts) of the ob formulation. Here, note that @cS @cK is read and evaluated the same as @cS(@cK).

NOTE 1: It must be kept clear that ‵(x) is not the same as the LISP QUOTE. It's more like an enquote operator, in that enclosures, once formed, are evaluated as the enclosed item without further (immediate) evaluation.

NOTE 2: The @ notation is a bit clunky here. Any ideas?

NEXT STEPS

I am reworking some material to deal with the need to interpret REPL input expressions a bit more carefully. I still like the idea of translating to an ob and running eval on it as a kind of semantics, but it is not that simple. In particular, that might be a reference implementation, but not a great way to teach writing REPL statements. And, I think, the semantics of the input expressions is easier to state than that.

The REPL input will be presented as a precedence language, with

  1. element level of primitives, lindies, \@name and ( ob-exp ) at the lowest level.
  2. singleton level 0 and ‵ on level 1 at the next level
  3. application level 1 and level1 level 2 (separated by spacing if not bracketing) at the applicative level
  4. pairing level 2 and level2 :: level3 above that.
  5. ob-exp level 3 and level 3 == level 3 above that (because of tradition and emphasis on extensional identity)

There will be additional syntax to accomodate [a1, ..., an] notations and also the different ways of parenthesizing in applicative expressions.

rnd0101 commented 6 years ago

Yes, I think I can follow the explanation. However, I believe the problem is not at the top-level evaluation border or has anything to do with text substitution (that is why I put quotes around textual), but it is with difficulty to control what to evaluate when: Something enclosures are about.

What bothers me is that current syntax seems not to put programmer in control of evaluation.

I always understood space equivalent to ::, but in the above there seems to be a special application semantics for @cS @cK attempted? Maybe, I missed something in the theory, but ap/ev were not exposed in the frugal - now you have suggested level 2 (application). The apply operation (even if it is just space or even putting terms one after one) needs some structural support. Are any changes needed at the structure as well? My gut feeling says there may be some, especially around rules for enclosures.

Can't comment yet on the other frugal syntax suggestions as the evaluation seems to need resolution first.

orcmid commented 6 years ago

@rnd0101 I always understood space equivalent to ::.

Asserting that was my terrible blunder.

It's true, at the eval(exp) level, that e1::e2 typically evaluates as ap(ev(e1),ev(e2)). But that's not the same as saying space is equivalent to "::" at the ob-exp level. That was a brain-fart on my part.

Everything I stated above about how cS, cK, and cSKcKC work as obs is accurate. The problem is getting it right at the REPL input expression level. This is not like the way original simple LISP handled a SEXPR on input.

The remedy is to treat application at the ob-exp REPL input level as different than having there be an overall transformation to an ob, exp, that is eval-ed and then reported as ob-result, an applicative-operation-free form of ob-exp.

Consider that what we have is an expression notation form, ob-exp that when evaluated yields a canonical ob. And what the REPL outputs is a direct ob-result presentation of that ob. If that ob-result is read into the REPL as an ob-exp input, the same ob would be determined and it would be reported with the same ob-result expression. That is, there is no further evaluation/reduction beyond simply yielding the previously-yielded ob. So there are no applications left to do.

The way @name is introduced as introducing an enclosure should be understood as if the ob-result for the bound object is inserted in the input ob-exp as an enclosure in parentheses. ob-result should probably be an enclosure always, but there is a tendency to omit that when a trace is the result output by the REPL. I need to be very careful here. This implies there is an eval that will happen, and that can't be right.

Are any changes needed at the structure as well? My gut feeling says there may be some, especially around rules for enclosures.

I think the definitions of obap.ap(p,x) and of eval(exp) are just fine. It's getting the REPL input-output complementarity correct that has to be straightened out.

I think those are the proper conditions. Now it must be established that this is not an over-constrained problem. I agree that there is a concern for when any eval happens, and on what. We must not require Schrodinger's cat.

I believe the treatment of @name as a level 0 element in an ob-exp is sufficient. The bound ob is simply used at that place in the evaluation of ob-exp. It's not part of the parsing. It's introduction of an ob into the ob-exp evaluation as an already-determined (semantic) element.

Let's sit back and stare at that for a while. I think it will be good enough.

rnd0101 commented 6 years ago

Yes, there is enough complexity in the theory to have this done right. New busy week is starting, so I will not have much time.

Myself I am usually trying to look at aesthetics as well. Is there any problem to omit @? Also I see storing (naming?) evaluated result versus storing definition dilemma.

Plus, if everything is an ob, is there a need to say ob x = ?

orcmid commented 6 years ago

@rnd0101 Plus, if everything is an ob, is there a need to say ob x = ?

It serves two purposes

  1. Secure upward compatibility if and when there is an extension above oFrugal that still allows obs as a specific abstract type, and when there are other kinds of bindings/assignments.

  2. Ensure no ambiguity with simple expressions, especially when "=" might be used.

Architecturally, it is one of those things that can be removed at some point but is difficult to add later.

orcmid commented 6 years ago

update 2018-01-15: Added ~ as a potential binding-name prefix and moved % to the unavailable list (saved for IRI coding in names where interchange has character-set constraints).

@rnd0101 Myself I am usually trying to look at aesthetics as well. Is there any problem to omit @ ?

If we allow the same identifiers to be used as lindies and as binding names, we have a problem with it not being clear and that it requires global context to know what identifiers are referenced. It also gets in the way when lambda is defined later. And the meaning of an ob-exp changes when re-introduced later.

Here's the problem. What is your thinking?

  1. At the REPL reader level, there are three kinds of symbols: primitives, lindies, and binding names. They must be unambiguously distinguished with collisions not possible.

  2. In obs, there are only primitives and lindies.

  3. How primitives and lindies are differentiated in the theoretical formulation does not matter.

  4. Whatever agreement there is for ob-exp for primitives and lindies will be used for output of resulting obs in ob-exp format.

  5. So whatever it is, the form for a binding name and for a lindy has to be different. Binding names operate entirely at the oFrugal level. They have nothing to do with oMiser and they do not appear in output.

  6. When lambda expressions are handled in eval (in oMiser, not oFrugal), lindies will be used to find the elements of the ob, taken as a script, that are be abstracted. They may also figure into data-type introduction later.

  7. For that reason, I think it is the binding name that needs a prefix in an ob-exp. I agree, @ is a bit ugly and is jarring, especially in simple examples.

  8. There seems to be only choice among ~ ! @ $ ^ & * : ? / and I'm not certain $ is uniformly available. The other keyboard-common characters are used for other purposes or should be saved for such purposes: # % ( ) - _ + = { } [ ] | \ ; " ' < >

Thoughts? It could be a suffix instead of a prefix, but we need some way to distinguish occurrence of a binding name in an input ob-exp.

orcmid commented 6 years ago

Also I see storing (naming?) evaluated result versus storing definition dilemma.

At the oMiser level, there are only obs. The distinction between scripts and data is only determined to the extent that an obs and its constituents are subject to use as operators or as operands in arriving at an obap.exp(exp) result. The determination is dynamic and in the context of the course of evaluation.

Consequently, there must not be a dilemma about this. I think the improved definition of ob-exp will resolve this. It must, it seems to me.

Let's take our time reflecting on this and on getting ob-exp right.

rnd0101 commented 6 years ago

Tired a bit for constructive reply, sorry.

One jerky reaction is that this syntax

( ( ‵(cS) ‵(cK) ) ‵(cK) ‵(cS) ) ‵(x)

is not good, IMHO.

The ob-exp vs ob-result is understood. Would be nice to have less verbose way to control application, as oMiser gives an impression of nice homoiconicity otherwise.

(i've attached numbers in the quote below to refer easier)

I think the definitions of obap.ap(p,x) and of eval(exp) are just fine. It's getting the REPL input-output complementarity correct that has to be straightened out.

  1. I am attached to having the REPL output be the text representation of a single ob, and not presented as an enclosure unless that is what the single ob is.
  2. I'm also attached to the idea that feeding that back in will construct an identical ob to the one that was presented in output, and it will be presented the same way once again.
  3. It will be the case that, if you want to incorporate that ob-result form into a new ob-exp as an operator or an operand, it may need to be parenthesized and also enclosed, depending on the ob-result form.

1 and 2 are self-evident. I consider number 3 wider than just on the shell input/output boundary: Just like in A-D-B example I use:

.D ("x" "y" "z") ("x" "y" "z")

vs whatever I need to do with the first part, which evaluates to .D, but does not work as .D (here I used question mark as places for possible enclosures, but those do not work either):

?(?.A ?.D ?.B) ("x" "y" "z") ("x" "y" "z")

In other languages and math I am used to assume that what is in the () evaluates before outer operands. That is why this is surprising default syntax. What is worse, I have no idea how to perform that computation, what to enclose, maybe, I need to pepper it with .ARG like in cK?

Using Python as analogy:

D(["x", "y", "z"], ["x", "y", "z"]) == A
A(D, B) == D
but surprisingly:
(A(D, B))(["x", "y", "z"], ["x", "y", "z"]) != A

Maybe, .A is not ecological enough for this use?

rnd0101 commented 6 years ago

Also completely different idea came to me. I think, I now understand what bothers me with re-using combinators in a simpler way. Nothing new: The fact that combinator evaluates to something, which no longer works as a combinator!

oMiser> .cK
INPUT: (.E :: .ARG)
OUTPUT: `(.ARG)

But now I understand why this feels wrong to me. If there is a support for applicative programming, then the combinator should stay itself unless it's applied to something. I do not know what does it mean in terms of rules/axioms, though. Maybe this is exactly some result of violating Church-Rosser property... However, when in Rome, do as the Romans do... (word play not intended).

orcmid commented 6 years ago

@rnd0101 One jerky reaction is that this syntax ( ( ‵(cS) ‵(cK) ) ‵(cK) ‵(cS) ) ‵(x) is not good, IMHO.

You are right. I was confusing what gets expressed as input to the oFrugal REPL, thinking that input ob-exp had to be transformed to an an ob, exp, that is supplied in eval(exp). It is important to forget all of that.

The interpretation of ob-exp as an expression that yields an ob works differently than that. The revised ob-exp explanation should fix this.

Assuming that cS and cK are either primitives or name bindings (however written as elementary terms), the ob-exp for representation of (S K) (K S) application to x would now be

((cS cK) cK cS) x

The oFrugal statements,

> ob cSKcKS = ((cS cK) cK cS); > cSKcKs x;

with cSKcKs written as a binding name. (For consistency, I just realized the notation should be the same in the ob definition statement and the ob-exp statement.)

The new ob-exp format will have applications carried out only where applicative operations appear. The operators :: and are non-applicative ob-construction operations, whether their operands involve applicative operations or not.

orcmid commented 6 years ago
oMiser> .cK
INPUT: (.E :: .ARG)
OUTPUT: `(.ARG)

Yes, the output should be .E :: .ARG assuming .cK is replaced with a binding name and not identified as a primitive ob (an individual).

And the following would be correct for the repaired ob-exp evaluation.

oMiser> ‵ .cK
INPUT:  ‵ (.E :: .ARG)
OUTPUT: ‵ (.E :: .ARG)
oMiser>  .cK "x"
INPUT:  (.E :: .ARG) "x"
OUTPUT: ‵ "x"

Is this helping?

Update: And this also,

oMiser>  (.E :: ARG) "x"
INPUT:  (.E :: .ARG) "x"
OUTPUT: ‵ "x"
rnd0101 commented 6 years ago

Sounds good. What will be the syntax for apply in the oFrugal (space?)?

Plus, making sure, those will not be part of obs, just part of abstract syntax tree oFrugal shell has? What is associativity (right/left)? (I guess, they will be less binding than ::?)

Also, can be good to have frugal exp encoding in obs, do we need .AP or we already have enough primitives for that?

orcmid commented 6 years ago

@rnd0101 Sounds good. What will be the syntax for apply in the oFrugal (space?)?

Yes, juxtaposition works, as does use of parentheses. Association is still right-to-left, but bracketing will be resolved first (and left to right).

The following ob-exp expressions all have the same effect:

((.cS "x") "y") "z"
.cS("x", "y") "z"
(.cs("x") "y") "z"
.cs("x", "y", "z")

Because of pure-lindy traces, the resulting ob is expressed, in ob-exp form, as

("x" :: "z") :: "y" :: "z"

showing only the essential parenthesis.

The applications are obtained by obap.ap(p,x) where called for in the evaluation of ob-exp to yield an ob.

Also, can be good to have frugal exp encoding in obs, do we need .AP or we already have enough primitives for that?

If an existing ob is taken as a procedure (via eval or via being the operator script, p in an ap), pairing signifies an apply operation there, just as it does with the .cS script ob. The only exception is with regard to the special forms where .C :: (x) :: y and .D :: (x) :: y signify as ob.c(x,y) and obap.d(x,y)

There is no need for .AP. There is need for .EV because it is necessary for conditional and short-circuit evaluations in obs taken as scripts.

I have some other duties over the next few days. The priority is repairing the description of ob-exp and maybe cleaning up the related Question issues.

rnd0101 commented 6 years ago

Ok... Mixing left and right associativity is a bit harder to implement due to technical limitations of parsimonious library (only right recursion), so maybe I will skip syntax sugaring at first ( I mean bracketing like f(x) ).

I feel like the notation becomes a bit harder to read also, so probably I will make a graph-viz diagrams for abstract syntax tree first so that it will be easier to discuss.

orcmid commented 6 years ago

There seems to be only choice among ~ ! @ $ ^ & * : ? / and I'm not certain $ is uniformly available. The other keyboard-common characters are used for other purposes or should be saved for such purposes: # % ( ) - _ + = { } [ ] | \ ; " ' < >

I'd like the choice of binding-name prefix nailed down, before I go much farther. I could probably wait a while longer. I am leaning to ^ right now. However, on some keyboards this might be a non-spacing character, that shows up atop another character.

With ^, the previous examples (my latest thinking) would be something like:

> ob ^cS = .C :: ‵.C :: (.C :: (.E :: (.C :: (.E :: .ARG) :: ‵.ARG))            
+                    :: ‵(.C :: (.E :: .ARG) :: ‵.ARG) ) ;
> ob ^cK = .E :: .ARG ;
> ob ^cSKcKS = (^cS ^cK) (^cK ^cS) ;
> ^cSKcKS x;
   x
> ((^cS x) y) z;
(x :: z) :: y :: z
> ^cS(x, y) z;
(x :: z) :: y :: z
> (^cS(x) y) z;
(x :: z) :: y :: z
> ^cS(x, y, z);
(x :: z) :: y :: z

My question is, what works better, ^ or another of the eligible characters, ~ ! @ $ ^ & * : ? /

rnd0101 commented 6 years ago

I have no preferences. Is it possible to reuse a dot as with primitives? .cS ? In my opinion, the less special syntax used the better. Prefix is better, because for example even in my simple implementation there is variable autocompletion feature.

orcmid commented 6 years ago

@rnd0101 I have no preferences. Is it possible to reuse a dot as with primitives? .cS ? In my opinion, the less special syntax used the better. Prefix is better, because for example even in my simple implementation there is variable autocompletion feature.

  1. OK, agreed on the prefix. The possibility of auto-completion is a valuable one.

  2. The weird thing for me is that primitive names are reserved and binding names are substituted. So, in an output expression, the primitive names must appear and the defined ones will be gone because they were expanded on evaluation of the ob-exp.

  3. I can get my head around this a little, but for the problem that if there are more primitives introduced in the future, they will clash with user-chosen binding names in processing of oFrugal source files that were created before then.

  4. Also, if oFrugal allowed the primitive names to be re-used as defined binding names, the problem is that output will still reflect them as the primitives, and re-input could be all wrong :).

I am tempted, nevertheless. Still (3-4) are a big problem.

Do you see how we can reconcile that?

PS: I also have another use for "." at the ob-exp at least and maybe at the in a canonical ob. I'm not ready to look closely into that. Need the primitive, lindy, binding-name cases resolved now though.

rnd0101 commented 6 years ago

Ok. Do we need single assignment as in some FP languages or is oFrugal purely for interactive work?

I have not looked deeply again in what is written above. I still do not quite understand whether application function can be encoded as part of ob structure or is it purely oFrugal artefact? I'd liked language to be homoiconistic, but it's up to you. Also, if we even have side-effects, time of evaluation will be crucial. Maybe, we can take some time to summarize the findings/ solutions above? My first concern is not syntactic sugar or assignment operation at all, but how to represent and enact evaluation, so that textually changing (A. D. B.) with D. will produce the same result, and combinators will not need deep-enclosuring...

orcmid commented 6 years ago

@rnd0101 I still do not quite understand whether application function can be encoded as part of ob structure or is it purely oFrugal artefact?

When an ob is being evaluated as a script in oMiser, a pair is taken as an application of the eval of the a-part to the eval of the b-part. In oFrugal, where an ob-exp is being evaluated, applications are carried out using obap.ap and that ob is used in the further evaluation of the ob-exp as required.

Look at how obap.eval(exp) uses obap.ev(p,x,exp) and how that goes back and forth with obap.ap(operator, operand).

Or, start at obap.ap(p, x), and see what happens when p = ob.c(operator,operand).

When an ob is being evaluated as a script in oMiser, a pair is taken as an application of the eval of the a-part to the eval of the b-part.

orcmid commented 6 years ago

Ok. Do we need single assignment as in some FP languages or is oFrugal purely for interactive work?

  1. There is no assignment in oMiser. It is purely applicative and only yields obs. Someone else has to hold onto them. oFrugal is an example of that.

  2. oFrugal has a statement language where the results of ob-exp evaluations can be tied to a binding name and those obs can be reused in subsequent ob-exp evaluations by binding-name reference. There is no retention beyond the oFrugal session. So I think it is, at least for 1.0.0, oFrugal is just the REPL and the ability to include files to process statements from. There might also be a dump format for fast reload with preservation of the bindings. That is a future option.

  3. There is no input-output in oMiser, and oFrugal can't do much about that. I expect extensions beyond oMiser (with different names) will have oMiser as part of the core but have some imperative/interactive behaviors, such as with input-output and stateful entities.

  4. oMiser is an instrument to see how far one can get with purely applicative operation, and how can one introduce interactive behaviors while salvaging as much of the assurances of oMiser as possible. That prospect is fuzzy for now and will remain so until we have our arms around a fully-operational oFrugal/oMiser arrangement.

orcmid commented 6 years ago

My big, HELP WANTED question right now is to decide which of ~ ! @ $ ^ & * ? / works as a prefix on binding names. I need to have something in documentation and user guidance. I am going to use ^ for now. I can use search-replace if another character is selected. I removed : from the list because of search-replace difficulties with "::" an also parsing in the presence of "::".

See ^ example for how that will look.

With regard to prefixing to distinguish categories of name-like terms, one can shuffle around which kind of name has no prefix. That might be an additional consideration.

Another Case of Interest

There is an interesting case for the introduction of lambda and rec abstraction. Those operators will be at the oMiser level. That is, there will be obs that are programs for them. I had been expecting to be able to write ob-exp statements something like

> ob ^cs = lambda.x lambda.y lambda.z (x :: z) :: y :: z;

where the .x notation with infix "." is simply short for (x) here.

This would end up with the same result as

> ob ^cS = .C :: ‵.C :: (.C :: (.E :: (.C :: (.E :: .ARG) :: ‵.ARG))            
+                    :: ‵(.C :: (.E :: .ARG) :: ‵.ARG) ) ;

To keep faith with how there are no reference to bindings at the oMiser level, This might need to be done with

> ob ^cs = ^lambda.x ^lambda.y ^lambda.z (x :: z) :: y :: z;

Note that (x ::z) :: y :: z could be written (x z) y z in this case, but only because that ob-exp evaluates to the pure lindy trace (x :: z) :: y :: z when the ob-exp is evaluated by oFrugal. That will certainly work. It can deceive newcomers into having an incorrect conceptual model about what is happening though. (I think I've already done that with @rnd0101 and, alas, myself ☹️ ).

Since the infix "." notation is purely at the ob-exp oFrugal level, I could have the rule be that lambda.x be evaluated as if written ^lambda(x) in this case and writing it as either ^lambda.x or lambda.x would be acceptable.

I am entertaining that notion, using whatever prefix is finally chosen for binding names.

rnd0101 commented 6 years ago

FYI, I've added simple graphviz functionality, and here is cS in ob structure:

omiser

rnd0101 commented 6 years ago

I have read the https://github.com/orcmid/miser/commit/f0fe680e5352d8ad3ba37d9808b47f0d68a43889 and I think it's clearer now. There are some facts not reflected there (like, associativity and priority of operations), plus some syntactical peculiarity (eg, what does empty[] means? What does : means before the closing ]? ), plus now there is that argument list notation (a,b,c).

So basically what I understood frugal shell no longer treat input as a single (complex) ob as it used to be the case. So basically a::(f (x::z)) involves, correct me if I am wrong, an ob x::z, f, and a, and then application of f to x::z, and then constructing new ob, which is the a pair of a and the result of application of f to x::z, right?

I am still to digest the fact oFrugal input does not have single ob encoding, or does it? As a practical matter, I wonder if the result of parsing by oFrugal shell should be still some single ob, which encodes the applicative structure in a way that applying obap.eval to that ob has the same result as doing applications/constructions "inline"?

rnd0101 commented 6 years ago

Syntax comment.

Above the following form is used as assignment:

ob ^cS = ...

I have two many thoughts:

  1. The caret reminds lambda a bit - maybe, it can be used for lambda instead? ^x.x
  2. I was also thinking of "abuse" for ^: if it's an operation.

^"lindy" can evaluate to ^lindy, and ^(....lindy as result of this...) will access variable, also to the left of assignment. But this will probably give oFrugal so much expressive power, that will shadow oMiser totally... Pragmatics of ^(...lindy trace...) is mind-boggling.

We can also represent the whole workspace as an associative array (like the one you called langNames), so variable access can be seen as getting element of name space... So basically ob ^var statement syntax can be replaced with expression syntax like setcar in LISP for a namespace ob... Extensions to other namespaces are then obvious. This of course introduces side-effects, but I see it as a road to representing IO, streams and all the usual stuff. Making "assignment" an expression instead of statement have its pros and cons of course.

That lindy-to-variable-name can be used to have no special syntax for variable access at all: get(ob, "var") can be a way to access variable from whatever namespace (ob in this case). The get itself can be yet another primitive, eg G: that is: .G(.ob, "cS") ... Where .ob is that workspace dictionary (in Python-speak) you mention in ob.txt. This maybe sugared as in Python .ob["cS"], but why not .ob^"cS" or even .ob may be made a context, so it becomes ^"cS" and if lindies are not in quotes - exactly your proposal ^cS. Or maybe ob can be name in it's own namespace, so we can drop that dot. (As you can notice, I do not consider ob as a type definition but namespace, so basically ob.var is also an option, but it conflicts with use of dot for primitives: Good language should have some "error-correcting" capabilities, especially, dot is easy to misread). What do you think?

I guess, .G is possible to have as a lengthy oMiser script, and would be nice for the cookbook, and along with combinators, fix, lambda (?), can be part of the standard library.

There is also syntactic axis to the oFrugal. Python can't add new syntax, but for example Forth does it routinely, so the oFrugal syntax itself can in principle start with minimal set of "commands", and the rest can be built with the obs, which will have influence on the lexer / parser itself... This is fascinating avenue for oMiser/oFrugal expansion, and I'd even said probably the fastest to achieve expressive power quickly (Forth is good example again: You create your language compiler first, the you use it). Too much for this thread, sorry.

My point is to find more oMiserish way to express higher-level constructs because it may have certain benefits in describing language in itself... if it is valuable property to you at all.

rnd0101 commented 6 years ago

One small note: In my frugal shell I made :: at lower level than application. Now after rereading https://github.com/orcmid/miser/issues/10#issuecomment-357536540 I think maybe it's better my way:

that is A B::C is A (B::C) and A::B C is (A::B) C.

?

orcmid commented 6 years ago

@rnd0101 A B::C is A (B::C) and A::B C is (A::B) C.

I had the idea of doing it the other way. But looking at your examples, I am inclined to the left case.

There is another way, and that is with APL grouping, where the right operand is more extensive than the left. Then, A B::C is A (B::C) but A::B C is A::(B C). I don't like how non-obvious that is though.

Still thinking ...

rnd0101 commented 6 years ago

Right-to-left is natural to building pairs, but application can be operation with less priority (not equal to that of ::) because application is outside ob-construction... The only reason to have left-to-right for application is that it has less surprises for reader/programmers: eg, combinators can be just copy-pasted.

APL had so many operations that it was probably the only sane way: To declare them all equal and same direction.

orcmid commented 6 years ago

There is mathematical history around right-to-left, as in trigonometry. It appears that Church favored it initially (although he was careful to use brackets heavily), and then he switched to the form that was used in combinatory logic. The difference for us is that combinators are not the domain of discourse, obs are and that has influenced my thing about how we should look at expression of combinator representations.

I don't think the folks that already know combinatory logic are the target group here. Of course, there is the left-to-right pattern in some functional programming languages, independent of combinators.

For Question #8, I want to look at multiple sources on this, and also having more practice using the way :: and have been introduced.

PS: It is an oddity that OOP languages having the Java form of method chaining are left-to-right, but that would apply here as well. Although I don't think forms like Cows.hereford(12)[1].moo() will be that common, though we could have something like that.

orcmid commented 6 years ago

@rnd0101: what does empty [] mean?

I was thinking not to allow it. The alternative is to use the common case of ob.NIL and that is probably worth doing.

I hadn't intended to use () as an argument-list form. If I did, it would be having ob.NIL as a default operand. Feels a little too hacky to me as a way of getting an eval. Thoughts?

What does: mean before the closing ]?

The idiom for lists is with chaining down the ob.b-path until a singleton is reached, as with the default use of ob.NIL at the end, and hence as the empty-list case.

The [..., (something):] form specifies the continuation beyond what is presented explicitly, usually but not necessarily the singleton that is the terminator. That is the case of [ ..., ‵default:] in the ob.txt examples (1-2).

rnd0101 commented 6 years ago

Some time ago I was looking into Elixir programming language (it's built on top of Erlang machine BEAM). It has "pipe operator" - left |> right, which helps build data pipeline as it goes from one operation to another, and unless you are Arabic and such, in natural reading order.

rnd0101 commented 6 years ago

Regarding [] above. I've not noticed that : before ], and not quite sure what to do with it now. Yes, I know that NIL-ending lists are there to signify empty list, but reading through all the docs here, I am still wondering whether oMiser / oFrugal is really into having each "list" ending in NIL?

Either way, what the presence or absence of : before closing ] should change? Right now, there are no implicit NILs added in "list" sugar. If NIL is needed, it can be easily added explicitly. (I am so used to Python's Zen ) Please, clarify.

The [] can be equivalent to NIL maybe still but pairs are self-sufficient in oMiser, IMHO, or?

As for () and default operand, for me arity in oMiser is a bit of mystery still. Primitives are zero arity, e is 1, c is 2 - this is clear, but there is certain mix-up when pairs come into play. Eg, f (a::b) vs. f (a, b). And I mean oMiser encoding way. This is purely based on "feeling". So I do not have any definite thoughts on what () can mean, maybe indeed application to NIL?

That actually reminds me of the point of having some algebraic structures in mind. Eg, NIL or [] can be zero of semigroup on concatenation. Having solid algebraic structures as a foundation makes things natural, not hacks.

orcmid commented 6 years ago

There is useful discussion on the consideration of various aspects of what the expression grammar and other aspects of the oFrugal REPL are.

These are mainly handled, now, in Question #8 and Question #14. The ideas have been worked out now in the oFrugal grammar. I am closing this issue, but adding a reference to it in #8 and #14.