Importing files in GNU Smalltalk
Asked Answered
B

2

8

I'm new to GNU Smalltalk. I know that in most programming languages, there's an import/#include/require command that gives one source file access to the contents of another. My question is, how do I import one file into another in GNU Smalltalk? Any help would be appreciated. Thanks!

Boeke answered 6/10, 2016 at 16:42 Comment(1)
I have updated my answer to include packaging.Blest
B
6

I think there are two good answers:

  1. FileStream fileIn: 'afile.st'.

  2. In GNU Smalltalk, you don't import/include/require one source file into another source file.

Explaining the first answer

Suppose I have the file foo.st:

"foo.st"
Object subclass: Foo [
    foo [^ 'I am Foo from foo.st']
]

If I want to use the class Foo from the code I am writing inside bar.st, I can use FileStream fileIn: 'foo.st' in the init method of Bar:

  "bar.st"
  Object subclass: Bar [
    | barsFoo |

    Bar class >> new [
        | r |
        r := super new.
        ^ r init.
    ]

    init [
        "Combines the contents of foo.st with the current image
         and assigns a new Foo to  barsFoo."
        FileStream fileIn: 'foo.st'.
        barsFoo := Foo new.
    ]

    bar [
        ^ 'I am bar from bar.st'
    ]

    foo [
        ^ barsFoo foo.
    ]
]

Using these classes looks like:

$ gst
GNU Smalltalk ready

st> FileStream fileIn: 'bar.st'
FileStream
st> b := Bar new
a Bar
st> b foo
'I am Foo from foo.st'

So far, it all looks like an ordinary import/include/require. But it's not really because the FileStream fileIn: 'foo.st' inside of init occurred at runtime and so I can type:

st> f := Foo new
a Foo
st> f foo
'I am Foo from foo.st'

Explaining the second answer

The reason I get a new Foo after importing bar.st is because FileStream fileIn: 'bar.st' combines the contents of bar.st with the current image.

Though GNU Smalltalk uses the abstraction of source code files. The underlying abstraction of Smalltalk is the image not the file and that is as true of GNU Smalltalk as any other Smalltalk system. The lack of a traditional IDE does not change the primacy of the image. For me, this has been a hard abstraction to get my head around as a new Smalltalk user in general and a new GNU Smalltalk user in particular.

This means that the ordinary low-level step-by-step method of managing Bar's dependency on Foo is by creating an image that already includes Foo first:

$ gst
GNU Smalltalk ready

st> FileStream fileIn: 'foo.st'
FileStream
st> ObjectMemory snapshot: 'foo.im'
"Global garbage collection... done"
false
st>

Now I can start the image that already contains Foo:

$ gst -I foo.im
GNU Smalltalk ready

st> f := Foo new
a Foo
st> f foo
'I am Foo from foo.st'

So long as I am not actively developing Foo in parallel with Bar I can change its init to:

init [
  barsFoo := Foo new.
]

I could also create a new image with both Foo and Bar:

$ gst -I foo.st
GNU Smalltalk ready

st> FileSteam fileIn: 'bar.st'
FileStream
st> ObjectMemory snapshot: 'foobar.im'

Building a system from latest source

It is possible to create an object that reads the latest version of both files from disk:

Object subclass: FooBarBuilder [
    FooBarBuilder class >> new [
        | r |
        r := super new.
        ^ r init.
    ]

    init [
        FileStream fileIn: 'foo.st'.
        FileStream fileIn: 'bar.st'.
    ]
]

And build an image:

$ gst
GNU Smalltalk ready

st> FileStream fileIn: 'foobarbuilder.st' 
FileStream
st> ObjectMemory snapshot: 'foobar.im'
"Global garbage collection... done"
false
st> 

Using the new image foobar.im allows me to bring in the latest versions of Foo and Bar each time I create a new FooBarBuilder. Not exactly beautiful and kind of kludgey, but it will do some of the work of a real build system.

Packaging it all up

Instead of keeping track of the multiple images (foo.im, foobar.im) GNU Smalltalk's package system can be used to import all the necessary files into a 'clean' Smalltalk runtime. It starts by creating a package.xml file:

<package>
  <name>FileImport</name>
  <file>foo.st</file>
  <file>bar.st</file>
  <file>foobarbuilder.st</file>
  <filein>foo.st</filein>
  <filein>bar.st</filein>
  <filein>foobarbuilder.st</filein>
</package>

The next step is 'publishing' the package so GNU Smalltalk can find it using gst-package. Here I publish it to my home directory in Linux rather than the system wide location (/usr/share/gnu-smalltalk/ on Ubuntu 16.04):

~/smalltalk/fileimport$ gst-package -t ~/.st package.xml
~/smalltalk/fileimport$ gst
GNU Smalltalk ready

st> PackageLoader fileInPackage: 'FileImport'
"Global garbage collection... done"
Loading package FileImport
PackageLoader
st> Foo new
a Foo
st>

Concluding Remarks

There is no free lunch. GNU Smalltalk gains something by making it easy to work with files in familiar ways. The price is that files don't integrate particularly well with the abstraction of images and the expectation that development will mostly occur by modifying running images.

There's no free lunch. At some point the gains from traditional Smalltalk IDE's are likely to outweigh the familiarity of working with source code files due to the impedance mismatch.

Blest answered 17/8, 2017 at 20:9 Comment(0)
A
4

It might work without import/include/require/use because Smalltalk is late bound:

  • class names are resolved in a global namespace traditionally named Smalltalk which is (was) a SystemDictionary associating class name (key) to class (value). This key-value pair is a binding or association object depending on Smalltalk brand. The byte code generated by the compiler just pushes the binding (understand a pointer to, the binding is shared) which is stored in compiled method literals, and extract it's value.
  • if a class does not yet exist, then the binding will be stored into a special dictionary named Undeclared. If this undeclared variable is later defined, then the definition is changed (that is the value is changed), and the binding moved to the system dictionary.
  • as for method names (so called selectors), they are not resolved at all until runtime: the bytecode produced by the compiler is: push the receiver. push the arguments. send the selector.

What enables late binding is that you can only interact with an object by sending a message. And message lookup is performed at runtime by searching a key corresponding to the selector in receiver's class methodDictionary.

However, load order is important when processing the class side initializations. An undeclared binding is initialized with nil, so sending a message to nil might probably be inapropriate and cause a MessageNotUnderstood exception.

So gnu smalltalk added the notion of package. It's kind of meta-data describing the dependency and directing the load order, and eventually putting the definition into an alternate namespace.

Most of this answer is from basic principles behind Smalltalk-80.
It may vary in latest gnu implementations.
See

Last thing: in Smalltalk-80 the fact that source code was stored into a file or another was an implementation detail hidden from user.
You would code directly in a Browser.
Eventually you would export a method/class/category into a fileOut but would never write this file by yourself.
GNU Smalltalk is a bit hybrid with this respect.

Acquiesce answered 6/10, 2016 at 20:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.