Closed doyougnu closed 1 year ago
Ah! of course the paths would be different on CI.
@gelisam is there a concept of $KLISTERROOT? so we could change the paths to $KLISTERROOT/examples/...
. We could also add post-hook
s to the testsuite and mark these tests as skip, or fragile or expect to fail, or something like that.
is there a concept of $KLISTERROOT?
Kind of, there is a $KLISTERPATH which is usually set to the examples
folder so that files outside of the examples
folder can access the libraries from the examples
folder:
$ pwd
/home/gelisam/working/haskell/klister
$ cabal run klister -- run toy.kl
Module not found: define-syntax-rule.kl
Searched in directory at /home/gelisam/working/haskell/klister
And on $KLISTERPATH:
ExitFailure 1
$ export KLISTERPATH=`pwd`/examples
$ cabal run klister -- run toy.kl
Example at toy.kl:9.1-335.1:
(λx. let x = #[toy.kl:6.20-6.25]<other> in x 5) : Integer ↦
5
But in principle KLISTERPATH could just as well be set to any other path if the user has their own folder of libraries, or to a list of colon-separated paths.
Circular imports while importing "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl"
Ah! of course the paths would be different on CI.
We had this problem before, and we solved it by changing the implementation so it only prints the basename of the file instead of the full path.
We had this problem before, and we solved it by changing the implementation so it only prints the basename of the file instead of the full path.
hmm this does not seem like a clearer approach:
circular-1: FAIL
Test output was different from 'examples/non-examples/circular-1.golden'. Output of ["diff","-u","examples/non-examples/circular-1.golden","/tmp/circular-11576145-55.actual"]:
--- examples/non-examples/circular-1.golden 2023-03-02 21:36:24.865466787 -0500
+++ /tmp/circular-11576145-55.actual 2023-03-07 21:16:27.803397769 -0500
@@ -1,5 +1,2 @@
-Circular imports while importing
- "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl"
- Context:
- "/home/doyougnu/programming/klister/examples/non-examples/circular-2.kl"
- "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl"
+Circular imports while importing "circular-1.kl"
+ Context: "circular-2.kl" "circular-1.kl"
The entire pathname is pretty useful. Imagine if we had examples/A/foo.kl
and examples/B/foo.kl
, then just the filename will produce the error Circular imports ... Context: "foo.kl" "foo.kl"
. I think the real problem is that modules are tied to the filesystem at all. In contrast common lisp does not tie these two concepts together. Instead it treats a module as a first class concept and allows one to create a module out of a single file or across several files, or have a single file which defines several modules (although i'm not sure that's a good idea). In any case, I like the idea of not tying modules to files, this would allow klister to avoid the module re-exports that Haskell has. BTW what is the module story for klister? I assumed they are tied to files because of:
data ModuleName = ModuleName FilePath | KernelName KernelName
deriving (Data, Eq, Ord, Show)
We had this problem before, and we solved it by changing the implementation so it only prints the basename of the file instead of the full path.
hmm this does not seem like a clearer approach:
circular-1: FAIL Test output was different from 'examples/non-examples/circular-1.golden'. Output of ["diff","-u","examples/non-examples/circular-1.golden","/tmp/circular-11576145-55.actual"]: --- examples/non-examples/circular-1.golden 2023-03-02 21:36:24.865466787 -0500 +++ /tmp/circular-11576145-55.actual 2023-03-07 21:16:27.803397769 -0500 @@ -1,5 +1,2 @@ -Circular imports while importing - "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl" - Context: - "/home/doyougnu/programming/klister/examples/non-examples/circular-2.kl" - "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl" +Circular imports while importing "circular-1.kl" + Context: "circular-2.kl" "circular-1.kl"
The entire pathname is pretty useful. Imagine if we had
examples/A/foo.kl
andexamples/B/foo.kl
, then just the filename will produce the errorCircular imports ... Context: "foo.kl" "foo.kl"
. I think the real problem is that modules are tied to the filesystem at all. In contrast common lisp does not tie these two concepts together. Instead it treats a module as a first class concept and allows one to create a module out of a single file or across several files, or have a single file which defines several modules (although i'm not sure that's a good idea). In any case, I like the idea of not tying modules to files, this would allow klister to avoid the module re-exports that Haskell has. BTW what is the module story for klister? I assumed they are tied to files because of:data ModuleName = ModuleName FilePath | KernelName KernelName deriving (Data, Eq, Ord, Show)
Ah we probably inherited this from racket: https://docs.racket-lang.org/guide/module-basics.html Chez also doesn't associate modules to files: https://cisco.github.io/ChezScheme/csug9.5/syntax.html#./syntax:h0
Imagine if we had
examples/A/foo.kl
andexamples/B/foo.kl
Don't worry, that can't happen! #126 " 🙃
Seriously though, printing the basename instead of the full path in order to avoid CI issues is obviously a hack. I think hacks are still fine at this stage of the project, but if you want a proper solution, I can think of a few:
klister run
.circular-1.kl
and circular-2.kl
, without the shared prefix /.../non-examples
, and A/foo.kl
and B/K.kl
, without the shared prefix /.../examples
but with the important A
and B
path fragments.Ah we probably inherited this from racket: https://docs.racket-lang.org/guide/module-basics.html
Racket recommends using one file per module, but it supports defining multiple modules in a single file. I don't know if Racket supports spreading a single module over multiple files.
BTW what is the module story for klister? I assumed they are tied to files because of: [...]
Yes, modules are currently tied to files. I don't think it would be too hard to change that if we wanted, but looking at the advantages vs the disadvantages, I am not yet convinced it's worth it.
advantages:
.Internal.*
modules and to use re-exports to expose everything as a single module, whereas in Chez several files can contribute to the same module?disadvantages:
foo
can be defined in any file, not just in a file named foo.kl
, then only printing the module name and not the path might make it difficult for the user to find the source code for that module.(import 'foo)
, then the compiler will similarly have a hard time finding the source code for foo
. I guess the compiler already needs to have previously compiled the file which provides the foo
module, so it can look up foo
in some database of modules? That would require implementing #118 first, I guess.foo.kl
which defines module foo
, then reloads the file bar.kl
which imports module foo
, and then gets confused because their changes are ignored.foo1.kl
and foo2.kl
both contribute to the foo
module, foo2.kl
should be able to see the foo1.kl
definitions but not the other way around (or vice-versa). But how is that specified? foo2.kl
would have to import foo1.kl
, no?One alternate design for modules I would be more excited about would be sub-modules, in the style of Agda. That is, a file foo.kl
can define top-level definitions add
and multiply
, and also some top-level submodules moduleA
and moduleB
providing addA
and addB
respectively. Then, the user can import foo.kl
and then selectively import the sub-modules they need:
(import "fool.kl")
(import moduleB)
(example (add 43 (addB 2 2)))
One alternate design for modules I would be more excited about would be sub-modules, in the style of Agda. That is, a file
foo.kl
can define top-level definitionsadd
andmultiply
, and also some top-level submodulesmoduleA
andmoduleB
providingaddA
andaddB
respectively. Then, the user can importfoo.kl
and then selectively import the sub-modules they need:(import "fool.kl") (import moduleB) (example (add 43 (addB 2 2)))
Yes I had something similar in mind, but in agda don't you have to explicitly open
the module in order to access addA
from module A
? For now I think the hacks are entirely fine. Discussing the module system should probably belong to #211 anyhow.
in agda don't you have to explicitly
open
the module in order to accessaddA
frommodule A
?
Yes, in Agda you can choose whether or not to open modules in order to decide whether to write names qualified or unqualified. So you could write any of the following:
import Foo
import Foo.moduleB
x : _
x = Foo.add 43 (Foo.moduleA.addB 2 2)
open import Foo
import moduleB
x : _
x = add 43 (moduleA.addB 2 2)
open import Foo
open import moduleB
x : _
x = add 43 (addB 2 2)
But Klister does not support qualified names (maybe we should?), so it does not currently make sense to distinguish between opened and unopened modules, any imported module is automatically opened.
Discussing the module system should probably belong to #211 anyhow.
Better yet, a dedicated issue for modules, so we can use #211 to discuss whether fancier modules should be on the roadmap rather than what our fancier modules should look like.
If working on Klister's modules, make sure to read "Composable and Compilable Macros: You Want it When?" by Matthew Flatt. Whatever we do should address the concerns in that paper, and importing a design from Agda won't.
In particular, a system with serious macros should have the following in its module system:
Right now, we have most of the basics of this kind of module system, but it's missing important parts. We have phase-segregated instantiation, our semantics support separate compilation, and we can strip compile-time dependencies if we want. We presently tie modules to files (with #lang
), but it would be good to allow submodules like Racket does. In Klister today, #lang
is hard-coded in the parser. In Racket, a file foo.rkt
with:
#lang bar
decl ...
is mostly short for a file
(module "foo.rkt" bar
decl
...)
The "mostly" is because the #lang
can also interpose a new parser for non-s-expression languages.
It'd be nice to do this in Klister too. I'd do it be replacing:
data ModuleName = ModuleName FilePath | KernelName KernelName
deriving (Data, Eq, Ord, Show)
with
data ModuleName = SubmodName ModuleName Text | FileName FilePath | KernelName KernelName
deriving (Data, Eq, Ord, Show)
allowing imports to ask for submodules.
Things like qualified imports can be attained by prefix-in
and the like.
after cc687854edf7f2e061e6a132935285d7f4566f9b we get:
circular-2: FAIL
Test output was different from 'examples/non-examples/circular-2.golden'. Output of ["diff","-u","examples/non-examples/circular-2.golden","/tmp/circular-21773107-53.actual"]:
--- examples/non-examples/circular-2.golden 2023-03-02 21:36:24.864466802 -0500
+++ /tmp/circular-21773107-53.actual 2023-03-09 12:44:43.586192090 -0500
@@ -1,5 +1,5 @@
Circular imports while importing
"/home/doyougnu/programming/klister/examples/non-examples/circular-2.kl"
Context:
- "/home/doyougnu/programming/klister/examples/non-examples/circular-1.kl"
- "/home/doyougnu/programming/klister/examples/non-examples/circular-2.kl"
+ "examples/non-examples/circular-1.kl"
+ "examples/non-examples/circular-2.kl"
Which seems to be what we want.
Although the approach is a bit heavy handed because it adds the pwd
of klister run
or klister repl
to World
. Why World
? because the location of the world should be part of the World. I think this lays a foundation for doing things like :cd
in the repl. If that's too much change then please feel free to request more :)
If working on Klister's modules, make sure to read "Composable and Compilable Macros: You Want it When?" by Matthew Flatt. Whatever we do should address the concerns in that paper, and importing a design from Agda won't.
onto the reading stack it goes!
alright I think I understand the failure now:
bound_vs_free.kl
failing?Because its the only test in failing-examples
that import do.kl
and bool.kl
, no other examples in failing-examples
use the import system.
do.kl
and bool.kl
?These are modules that are not in stdlib
! Rather they are defined in examples
so why does is this not found in $KLISTERPATH? According to the comments in the KlisterPath.hs
the module import checks:
These are not found because failing-examples
is a sub-directory of examples
and when module imports look for these modules they are not found in the same directory, then not found in stdlib (because these modules are not in stdlib), and then are not found on $KLISTERPATH. Now I expected these to be found on $KLISTERPATH, but when I print debugged the environment variable it actually was never set in the testsuite at all. So in ef01aaf467e2d28e7c6d3b135bf1b72951825e4d I set $KLISTERPATH
in the github CI but these modules were still not found.
$KLISTERPATH
even when it was set correctly?I think this is because findInDirectory
is written using listDirectory
:
-- KlisterPath.hs
findInDirectory :: String -> FilePath -> IO [FilePath]
findInDirectory moduleName dir =
map (dir </>) . filter (== moduleName) <$> listDirectory dir
but I have to verify this behavior still because findInKlisterPath
looks correct to me:
findInKlisterPath ::
KlisterPath {- ^ Path to search -} ->
String {- ^ Module to find -} ->
IO [FilePath] {- ^ Candidates found -}
findInKlisterPath (KlisterPath paths) moduleName =
concat <$> mapM (findInDirectory moduleName) (Set.toList paths)
Does this test succeed locally on your machine but fail on CI, or does it fail everywhere?
So in ef01aaf I set
$KLISTERPATH
in the github CI but these modules were still not found
Looks to me like you set it to ${github.workspace}
but you should set it to ${github.workspace}/examples
. Remember, $KLISTERPATH
is a search path for klister modules, it is not the path to the root of the klister repo!
Does this test succeed locally on your machine but fail on CI, or does it fail everywhere?
Everywhere. Looks like $KLISTERPATH
isn't set locally when I run cabal test
Looks to me like you set it to ${github.workspace} but you should set it to ${github.workspace}/examples. Remember, $KLISTERPATH is a search path for klister modules, it is not the path to the root of the klister repo!
Indeed b1e675050190e14f478b0fc8ce3db6e0a73d29ab works and this does confirm that findInKlisterPath
does not search sub-directories. IMHO I think it probably should. Thoughts @gelisam?
findInKlisterPath
does not search sub-directories. IMHO I think it probably should. Thoughts @gelisam?
I don't think it should, no, why? Is there any language which behaves that way?
Bash's $PATH
does not recursively search sub-directories:
$ cat foo
#!/bin/bash
baz
$ cat bar/baz
#!/bin/bash
echo "baz was executed"
$ PATH=`pwd` ./foo
./foo: line 2: baz: command not found
$ PATH=`pwd`/bar ./foo
baz was executed
Python's $PYTHONPATH
does not recursively search sub-directories:
$ cat foo.py
import baz
$ cat bar/baz.py
print("baz was imported")
$ PYTHONPATH=`pwd` python foo.py
Traceback (most recent call last):
File "/home/gelisam/working/foo.py", line 1, in <module>
import baz
ModuleNotFoundError: No module named 'baz'
$ PYTHONPATH=`pwd`/bar python foo.py
baz was imported
Java's $CLASSPATH
does not recursively search sub-directories:
$ ls bar/
Baz.java
Baz.class
$ CLASSPATH=`pwd` java Baz
Error: Could not find or load main class Baz
Caused by: java.lang.ClassNotFoundException: Baz
$ CLASSPATH=`pwd`/bar java Baz
Hello World!
Ruby's $RUBYLIB
does not recursively search sub-directories:
$ cat foo.rb
require "baz"
$ cat bar/baz.rb
puts 'baz was required'
$ RUBYLIB=`pwd` ruby foo.rb
<.../kernel_require.rb>:85:in `require': cannot load such file -- baz (LoadError)
from <.../kernel_require.rb>:85:in `require'
from foo.rb:1:in `<main>'
$ RUBYLIB=`pwd`/bar ruby foo.rb
baz was required
Closes out #42. This MR also:
cabal.project
file, although a minimal one at that.