[HN Gopher] A railroad simulation using discrete event simulation
___________________________________________________________________
A railroad simulation using discrete event simulation
Author : todsacerdoti
Score : 157 points
Date : 2024-06-23 14:16 UTC (1 days ago)
(HTM) web link (picolisp-explored.com)
(TXT) w3m dump (picolisp-explored.com)
| taddevries wrote:
| I'm just gonna say that I belive Factorio is the best railroad
| simulator. Second only to OpenTTD.
| nalzok wrote:
| How do you like Simutrans? https://www.simutrans.com
| TylerE wrote:
| I can't stand it because of the insanely low built in fixed
| framerate. It's something weird like 20fps. UI responsiveness
| is also terrible for the same reason, because everything is
| apparently spaghetti coded together and the game logic is
| tied to the framerate.
| ssl-3 wrote:
| Factorio is a wonderful video game that includes aspects of
| using railroads, but it is not a simulation of how railroads
| work on Earth.
| MadnessASAP wrote:
| It is however a simulation of how railroads work as
| predators.
|
| _Choo Choo_
| xedrac wrote:
| Factorio's railroads are so fun to build and play with. Add a
| circuit network and robots to the mix, and you have yourself
| endless hours of enjoyment. I eagerly anticipate the release of
| 2.0 this fall.
| jjmarr wrote:
| Factorio is a bad railroad simulator because you cannot do
| grade separation (bridges or tunnels). This means all of your
| rail networks are artificially limited to being planar graphs
| which kills your throughput.
|
| OpenTTD allows grade separation at least but imho is inferior
| to Simutrans which ensures cargo and passengers have
| destinations and will not use your network unless you can path
| them to their destination.
|
| Because OpenTTD doesn't have this it devolves into forcing
| cargo or people to travel the longest possible distance for the
| most money, instead of to where they actually want to go.
|
| Simutrans-extended is something I'm really hyped for because
| the simulation of passenger desires is even more in-depth than
| the original.
|
| https://simutrans-germany.com/wiki/wiki/en_extended_Passenge...
|
| The most satisfying rail network for me is one that has both
| complex form _and_ function.
| eterm wrote:
| You'll be pleased to hear Factorio 2.0 is adding a second
| rail layer: https://www.factorio.com/blog/post/fff-378
| NoboruWataya wrote:
| OpenTTD does have this: https://wiki.openttd.org/en/Manual/Pa
| ssenger%20and%20cargo%2...
| jjmarr wrote:
| Cargodist isn't the same as what I'm describing. It creates
| destinations for cargo and passengers based on the existing
| network you have. In other words, "demand" is changed to
| fit your playstyle.
|
| In regular Simutrans, passengers want to go to a specific
| destination _regardless_ of whether your network allows
| them to go there. This means you have to change your
| playstyle to fit what passengers want to do, since
| otherwise, people won 't take trips.
|
| I believe Simutrans handles this better. If you're someone
| who wants to go to Europe but the only plane ticket you can
| buy is to South America, you won't take that trip
| (Simutrans model). But in OpenTTD, passenger demand is
| basically "yeah I just want to go somewhere" and then you
| deliver them to Antarctica. Cargodist means that if you now
| offer service to Greenland, they'll evenly distribute
| themselves between Antarctica and Greenland.
|
| I'm more of a product-minded person so I prefer Simutrans.
| Demand is the constant I want to optimize around; I don't
| want demand to be optimized around my transport network.
| But this also creates a much more difficult game.
| RugnirViking wrote:
| The linked article of the other person (about cargodist)
| says it distiguishes itself from an opentd extention
| called YACD which seems to be more what you are
| suggesting.
|
| https://www.tt-forums.net/viewtopic.php?f=33&t=54253
| ssl-3 wrote:
| Factorio is a bad railroad simulator because it is was never
| designed to be a railroad simulator. This has only a little
| bit to do with the respective grade-levels of crossings
| (because, I mean, _many, many_ very functional rail networks
| actually-operate on Earth using only flat crossings, because
| the terrain is flat where these networks are built).
|
| One way in that vanilla Factorio is a shit simulation is that
| it cannot couple and decouple cars. Trains are built by hand,
| and then those trains remain as they are until remogrified by
| hand.
|
| Want to send coal/copper/iron/something (or a combination of
| things) somewhere else? Cool beans! People do this every day
| in the real world.
|
| In the real world, cars are often left in yards and sidings
| to be swapped around and loaded by switchers and other
| mechanisms while the locomotive that delivered these cars has
| departed -- probably along with a train of other cars that
| this station isn't interested in.
|
| In Factorio, one lets trains fill up with things at A
| (according to rules), and then it goes from A to B and unload
| those things at B (according to rules). Stops for C, D, and E
| can be added, but even if they are: The whole train
| (including locomotives) stays coupled together together and
| there isn't any other way to do it.
|
| The locomotive is _always waiting_ unless it is travelling
| _with the entirety of its assigned cars_. The trains are
| _completely inflexible_.
|
| Real-world train networks don't work that way. Got 50
| containers to load up? Drop off 50 appropriate cars to be
| loaded up, and move on to the next problem while that station
| deals with putting containers onto cars. And at that next
| station, load up on already-full tankers. And then to the
| next station where a bunch of new Fords are dropped off in
| segments of TTX transport cars.
|
| Factorio is also a shit simulation because these fixed-unit
| trains have a predefined route: Not only can cars not be
| picked up or dropped off, no station can offer things and no
| other station can order things. In Factorio, if there is iron
| to deliver: The usual method is to pick up as much iron as
| will fit on this inflexible train (however long that takes),
| and take it to station B to unload (however long that takes):
| It's a neat way to approximate how a belt works in Factorio
| over a longer distance, but it is not a simulation of how
| rail actually works.
|
| It's a fun game and I love playing it, but it's not a fucking
| rail simulator[1]. Very few aspects of Factorio's rail system
| resemble actual rail systems in the real world that actually
| exists.
|
| (Actually, while I'm at it: _Factorio isn 't a simulator of
| anything._ Just because it is a fun game does not mean that
| it has to be a simulation of...anything.)
|
| 1: https://www.merriam-webster.com/dictionary/simulator
| paxys wrote:
| So then...OpenTTD is the best railroad simulator?
| burgerrito wrote:
| Clearly you folks haven't tried playing A-Train, a Japanese
| train tycoon game...
|
| Seriously though, try it out! It's actually one of the best
| games I've ever played. I'd even say that it's so underrated.
|
| It's a train tycoon game, but also silently focuses on being a
| real estate simulator, just like how a lot of Japanese
| companies don't make money primarily on train tickets, but
| instead real estate or other non-farebox income.
| zem wrote:
| one of my favourite games ever was "railroad tycoon 2"; it
| was not a particularly deep railway simulator, but it was a
| superbly immersive treatment of "railroads helped settle a
| continent". few games achieved that sort of immersive feeling
| for me, civ 1 and stellaris are the other ones that come to
| mind.
| nathan_compton wrote:
| Picolisp - what a weird thing. I have to admire the temerity of a
| guy who in 2023 is still team "dynamic scope is better."
| actionfromafar wrote:
| It's like monkey patching in Ruby. It can be used for good and
| for evil.
| DemocracyFTW2 wrote:
| In a less balanced both-sides view Ruby as a social construct
| is an outlier, monkey patching being (rightly so IMHO)
| regarded as a more-than-questionable practice in most other
| mainstream PL communities. I mean, yeah, GOTO can be used for
| good and for evil, and so can GOSUB 200.
| nerdponx wrote:
| Wait, is it global by default (Lua, Bash) or truly _dynamic_?
| The latter would be kind of mind-bending to program with as the
| sole or default style. Was that ever a thing? Maybe I 'm just
| too young to have experienced that.
| markasoftware wrote:
| Emacs Lisp had dynamic binding as the default (without any
| true support for lexical binding) until 2012.
| tmalsburg2 wrote:
| People talk as if dynamic scoping was objectively a
| mistake, but the fact that it works well and is really
| useful in a complex piece of software like Emacs seems to
| suggest otherwise.
| p_l wrote:
| original opposition to lexical binding in Lisp circles
| was that lexical would be slower. That turned out to be
| false.
|
| Emacs Lisp explicitly kept to dynamic binding for
| everything because it made for simpler overriding of
| functions deep in, but resulted in lower performance and
| various other issues, and ultimately most benefit from
| such shadowing seems to be focus of defadvice and the
| like.
| kazinator wrote:
| I can understand why that objection would be raised,
| because lexical binding _is_ slower in code that is
| interpreted rather than compiled, compared to (shallow)
| dynamic binding. Under shallow dynamic binding, there isn
| 't a chained dynamic environment structure. Variables are
| simply global: every variable is just the value cell of
| the symbol that names it. The value cell can be
| integrated directly into the representation of a symbol,
| and so accessing a variable under interpretation very
| fast, compared to accessing a lexical variable, which
| must be looked up in an environment structure.
| DemocracyFTW2 wrote:
| A rather weak argument when you consider what kind of
| mechanisms (like a digital clock with working seven-
| segment display) people have been programming / put
| together in Conway's Game of Life; to me this does not
| suggest in any way or manner that GoL could ever be my
| favored platform to simulate a digital clock (or anything
| more complex than a glider for that matter). Likewise
| vacuum cleaners and toothbrushes have likely been made
| hosts for playing doom, and people accomplish all kinds
| of stuff like quines and working software in brainf*ck.
| None of these feats are indicative of the respective
| platform being suitable or the right tool for a sizable
| number of programmers.
| pjmlp wrote:
| As someone that used it in languages like Clipper, or
| Emacs Lisp, and ADL rule in C++ templates, cool to make
| programming tricks of wonder, a pain to debug when
| something goes wrong several months later.
| nathan_compton wrote:
| Few deny the utility of dynamic-style variables for
| certain kinds of programming. But it can be helpful to
| segregate that behavior more carefully than in a language
| where it is the default.
| actionfromafar wrote:
| I used to program in PicoLisp a long time ago but I have
| forgotten most of it.
|
| Hope you can make sense of this:
|
| https://picolisp.com/wiki/?firstclassenvironments
| nathan_compton wrote:
| >PicoLisp uses dynamic binding for symbolic variables. This
| means that the value of a >symbol is determined by the
| current runtime context, not by the lexical context in the
| source file.
|
| >This has advantages in practical programming. It allows
| you to write independent code >fragments as data which can
| be passed to other parts of the program to be later
| executed >as code in that context.
|
| This amuses me because while its technically _true_ this
| amazing feat is accomplished only by denuding of the code
| of substantial expressive power - namely the relation
| between the lexical denotation of the code and its meaning.
| I will say this - aesthetically, I prefer picolisp 's
| approach to Common Lisp's, which is to just paper over this
| problem with gensyms, packages, etc. Give me hygienic
| macros or give me death.
| More-nitors wrote:
| idk doesn't this mean I can't get any help from IDEs?
| code-completion? find-all-references?
| cess11 wrote:
| You'd probably be the only person using an IDE for
| development in Picolisp.
|
| The main author does (or at least did) a lot of
| development on a tablet, with his own software keyboard (
| https://play.google.com/store/apps/details?id=de.software
| _la... , which I've enjoyed for years on my handhelds, in
| part due to the tmux-arpeggio), and his own editor
| (https://picolisp.com/wiki/?vip ). I think most of us do
| something similar, using vim or vip, maybe on larger
| computers, but generally a pretty minimal setup.
|
| The REPL has string based completion, besides completing
| symbols it will also complete file paths. Development is
| heavily REPL based, you'd spend a lot more time
| inspecting the runtime than searching for string
| occurrences in files.
|
| From the REPL you'd also read the language reference,
| most likely in w3m, the preferred text web browser in
| this community. (doc 'macro) will open the reference on
| this entry if you started the REPL with 'pil +', where
| the + is a flag denoting debug mode. You can expect the
| web GUI framework to work rather well in w3m.
| kazinator wrote:
| Gensyms and packages are not required to make lexical
| scope work. Macros in an unhygienic macro system use
| these internally so that their expansions don't have
| unexpected behaviors in the scope where they are planted.
| The problems avoided by gensyms or packages are affect
| both dynamic and lexical scopes. A dynamic variable can
| be wrongly captured by an internal macro variable, not
| only a lexical variable.
|
| It may be there are solutions favored in Picolisp without
| using macros that would be done using macros in idiomatic
| Common Lisp, and so those solutions don't need gensyms
| and whatnot.
| nathan_compton wrote:
| My point is only that unless you are using a hygienic
| macro system the idea that you are manipulating _code_ in
| your macro is a (often white) lie. _Code_ has semantics,
| a meaning, and unless the object you manipulate carries
| those semantics with it (that is, the syntax objects of
| eg `syntax-case`) you 're just manipulating some _data_
| which has a necessarily superficial relationship with the
| code itself. Picolisp resolves this by simple
| "eliminating" lexical scope, which means that code really
| is trivially related to its denotation since the
| semantics of variable binding really are just "whatever
| is currently bound to this variable." Scheme resolves
| this by having syntax-transformations instead of macros:
| functions which genuinely manipulate syntax objects which
| carry along with them, among other things, information
| about their lexical context. Common Lisp accepts that
| most of the issues arising from the distinction between
| code itself and its nude denotation can be worked around
| and provides the tools to do that, but in Common Lisp one
| still transforms the _denotation_ of the code, not the
| code itself. From my point of view, if one is purely
| interested in the aesthetics of the situation, the Scheme
| approach is much more satisfactory. From a practical
| point of view, it doesn 't seem to be particularly
| onerous to program in, although the macros in scheme seem
| to lack the immediate intelligibility of the Common Lisp
| ones.
| kazinator wrote:
| You are manipulating fragments of _source_ code in a
| macro. Material in which tokens have been converted to
| objects and which has a nested structure. So, nicer than
| textual source code.
| nathan_compton wrote:
| I mean yes and no. In a CL Macro you are manipulating
| lists of symbols and other atoms and in a sense that is
| code. But code has some static properties (of which
| lexical bindng is one) which are not reflected in that
| structure and which you can break pretty easily in a CL
| macro. A scheme syntax object carries that lexical
| information which is so critical to the meaning of the
| code and because it does it is much harder to
| _accidentally_ manipulate the code in such a way that
| meaning of the code changes. It is exactly the static
| lexical binding semantics Common Lisp which introduce the
| conceptual tension in macro programming that requires the
| programmer to manually worry about gensyms. Because
| picolisp lacks lexical binding manipulating code lacks
| this complication (and, in fact, the complication of a
| macro system almost reduces to a trivial combination of
| quotation and evaluation).
| kazinator wrote:
| Programmers say that they are manipulating code when they
| go "vi foo.c" at their Unix prompt, so that's a bit of an
| upstream rhetorical paddle.
|
| > _It is exactly the static lexical binding semantics
| Common Lisp which introduce the conceptual tension in
| macro programming that requires the programmer to
| manually worry about gensyms._
|
| A dynamically scoped Lisp (like Emacs lisp by default)
| with those kinds of macros needs gensyms all the same. It
| isn't the lexical scope.
|
| When we have (let ((x 1)) (+ x x)), then regardless of
| whether x is lexical or dynamic, there is a lower level
| of binding going on. The _x_ in (+ x x) physically
| belongs to the enclosing (let ...). That is not lexical
| scope; it 's a fact about the position of the code pieces
| regardless of x being lexical or dynamic.
|
| This is why in that strategy for implementing hygienic
| Scheme macros that you're alluding to, syntax objects,
| there is a different kind of closure at play: the
| syntactic closure. It is not a lexical closure.
|
| The syntactic closure doesn't say that "x is bound as a
| variable". Only "this x expression is meant to be
| enclosed in this code".
|
| Picolisp doesn't run into hygiene issues requiring gensym
| because it doesn't perform macro expansion:
|
| https://picolisp.com/wiki/?macros
|
| If you don't have a code manipulating process that
| invisibly transplants pieces of code from here to there,
| then of course you don't have the issues which that
| entails.
| nathan_compton wrote:
| Lisps sure is fun! I didn't understand any of this kind
| of stuff until I learned Lisp.
| bsder wrote:
| It's a shame that John N. Shutt is no longer with us.
|
| He created a very Scheme-like language called Kernel that
| seemed to walk the line between lexical and dynamic in a much
| more controlled way.
|
| https://web.cs.wpi.edu/~jshutt/kernel.html
| nathan_compton wrote:
| I think Kernel is the other side of the extreme from
| Picolisp, since it wants all objects to be first class but
| wishes to maintain lexical scope information for all of
| them. I think this is hard because in a certain sense the
| _names of things_ in a program have no natural
| correspondence to the meaning of the program from the point
| of view of a compiler writer in particular. Code calculates
| a value using values or changes the state of memory or
| however you want to conceive of it. The names one used to
| tell the compiler how to do that don 't have any obvious
| relation to the transformation and keeping them around so
| that the programmer can meta-program is complex and makes
| the generated code slower. In a way, Common Lisp and Scheme
| seem like two distinct local maxima, out of which I prefer
| the latter. Kernel is neat though.
| bsder wrote:
| Kernel is "mostly" just lexical unless you explicitly
| opt-out with FEXPRs. FEXPRs are what draw most people
| into Kernel.
|
| However, what is probably more important but doesn't
| immediately stick out until you poke at Kernel a lot
| harder are the fully reified "environments".
| "Environments are copy on write that don't destroy older
| references" has _very_ subtle consequences that seem to
| make dynamic scope a _lot_ better behaved.
|
| This also has the consequence that I can force things to
| explicitly evaluate in reference to the "ground
| environment" which is an explicit signal that it can
| _always be compiled_.
|
| I suspect there is a lot of fertile research ground here.
| In addition, there is a lot of implementation subtlety
| that I'm not sure he really grasped. Environments need a
| special data structure otherwise they cons up an
| _enormous_ amount of garbage (I suspect it really needs a
| Bitmapped Vector Trie like Clojure).
|
| I wish Shutt were still around to talk to. :(
| cess11 wrote:
| Of all the quirks and weirdness in Picolisp, this is what gets
| to you?
|
| Either a Picolisp system is shortlived enough that your
| execution environment actually maps to the file your loading,
| or you're going to be interactively inspecting it anyway.
| zem wrote:
| honestly, I feel the same way. for some reasons dynamic scope
| just feels "wrong" rather than quirky or weird, in that it
| doesn't fit my mental model of how a programming language
| should behave and what sort of bookkeeping needs to be the
| compiler's problem rather than my problem. never used
| picolisp, but I _really_ wanted to like lush back in the day
| and the dynamic scope was the stumbling block.
| cess11 wrote:
| There is no compiler in Picolisp, only a very simple
| interpreter.
|
| The only bookkeeping problem I've encountered in practice
| is littering the runtime with symbols. As far as I know
| there's no way to make it forget about symbols it has
| encountered, and they are interned as soon as they are
| encountered. I think the namespacing is supposed to counter
| this, but I've never learnt it properly.
|
| I'm not sure in what situation the scoping would be a
| problem. The way I usually go about picolisping is using
| maybe a few globals (could be options, credentials, global
| stacks, something like that), conventionally marked with an
| asterisk, like *Global, and then everything else is
| functions that typically take one parameter, or perhaps a
| data parameter and a numeric limit for an iterator. Besides
| let-assignment inside functions variables rarely seem to be
| the right tool for me.
|
| Lush, if it's the Lisp-like shell thingie, seems like a
| rather different programming environment, what with the
| inline C and whatnot. Might try it out, seems it hasn't
| been updated in fifteen years or so, could be an adventure.
| zem wrote:
| lush was basically an early attempt at what julia does
| today - a high level lisp like language and a high
| performance c-like language with good ffi support wrapped
| in one.
|
| I do see your point about picolisp being simple enough
| that the dynamic scope fits into the overall model; I
| might give it a try sometime just to see what it's like
| in practice.
| mppm wrote:
| It's not just temerity I think. Picolisp is a very different
| animal compared to all modern lisp and scheme flavors. It is
| the last "true lisp" that I am aware of -- it has an ultra-
| minimalist interpreter (hand-written in assembly, by the way)
| that actually represents programs as linked lists. A function
| is really just a list with the first element contains the
| argument and the second one the body, and the arguments are
| bare symbols. Picolisp has no compiler (not even a bytecode
| compiler), no lexical analysis and no other preprocessing.
| There is only the reader and the output goes directly to the
| interpreter. On the upside, this makes Picolisp the only
| language with truly "first class" functions in the sense that
| you can really create and manipulate them at runtime just like
| you would strings or integers, unlike pretty much every other
| language out there, where "lambdas" are just syntax sugar over
| function pointers. On the downside, this is all of course
| completely unchecked and pretty unsafe, and, to come back to
| the original point, you do not have such conveniences as
| lexical scoping. That would be literally impossible to
| implement without changing the nature of Picolisp into a proto-
| compiled language.
| lispm wrote:
| > It is the last "true lisp" that I am aware of -- it has an
| ultra-minimalist interpreter (hand-written in assembly, by
| the way) that actually represents programs as linked lists.
|
| Strange, I thought many Lisp systems still have source level
| list-based interpreters. For Common Lisp I would think: SBCL
| (optional), CLISP, Allegro CL, LispWorks, ECL, ... They can
| also compile code. Compiling Lisp code was also already
| available in the first Lisp implementations and having a
| compiler was an explicit goal of the original implementors.
|
| Let's use the LispWorks Listener (the REPL tool):
| CL-USER 25 > (defun foo (bar) (break) (print (list :hello
| bar))) FOO CL-USER 26 > (foo 10)
| Break. 1 (continue) Return from break. 2
| (abort) Return to top loop level 0. Type :b for
| backtrace or :c <option number> to proceed. Type
| :bug-form "<subject>" for a bug report template or :? for
| other options. CL-USER 27 : 1 > :bq
| INVOKE-DEBUGGER <- BREAK <- FOO <- EVAL <- CAPI::CAPI-TOP-
| LEVEL-FUNCTION <- CAPI::INTERACTIVE-PANE-TOP-LOOP <-
| MP::PROCESS-SG-FUNCTION CL-USER 28 : 1 > :n
| Call to INVOKE-DEBUGGER CL-USER 29 : 1 > :n
| Call to BREAK CL-USER 30 : 1 > :n
| Interpreted call to FOO CL-USER 31 : 1 > :lambda
| (LAMBDA (BAR) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash
| Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (BREAK)
| (PRINT (LIST :HELLO BAR)))
|
| The debugger output of the currently running function looks
| like a linked list to me. I could modify it destructively, if
| I wanted.
|
| LispWorks has a list-level interpreter. One can also compile
| code.
|
| Typically Lisp interpreters tend to be written in C, since
| that usually is more portable than Assembler.
|
| > On the downside, this is all of course completely unchecked
| and pretty unsafe, and, to come back to the original point,
| you do not have such conveniences as lexical scoping.
|
| I would expect from a typical Lisp interpreter (a source
| level list-based interpreter) that it does all kinds of
| runtime checks and also provides lexical scoping. If there is
| a clojure, then this closure would be a combination of some
| function and an environment. In standard Common Lisp there is
| no access to that environment, but I could look from an
| inspector into it: CL-USER 54 > (defun
| example (a) (lambda () (break) (print a))) EXAMPLE
| CL-USER 55 > (example 10) #<anonymous interpreted
| function 8020001A59> CL-USER 56 > (describe *)
| #<anonymous interpreted function 8020001A59> is a
| TYPE::INTERPRETED-FUNCTION Code (LAMBDA
| NIL (BREAK) (PRINT A)) Environment ((A . 10)
| (#:SOURCE-LEVEL-ENVIRONMENT-MARKER FUNCTION NIL . #<EQ Hash
| Table{0} 81D03EFF03>) (#:FUNCTOR-MARKER LAMBDA (A) (DECLARE
| (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>))
| (DECLARE (LAMBDA-NAME EXAMPLE)) (LAMBDA NIL (BREAK) (PRINT
| A))))
|
| As we can see, the interpreted closure has code a list and an
| environment, where A = 10.
| mppm wrote:
| > The debugger output of the currently running function
| looks like a linked list to me. I could modify it
| destructively, if I wanted.
|
| I doubt that actually, though I don't have LispWorks
| installed to try it. Modifying a function at runtime as if
| it were a list is actually the best test to see if your
| Lisp _really_ represents functions as lists, or as some
| other internal object that is rendered as a list in the
| REPL by accessing the stored definition. E.g. both CLISP
| and guile error out if I try `(car (lambda (a b) (+ a
| b)))`.
|
| Another good test is to construct a function at runtime and
| try to call it. To do that you will probaby need to call
| `eval` or equivalent, just like you would in Lua or Python.
| Not in Picolisp though, which is why I consider it to be
| the only truly homoiconic programming language.
| lispm wrote:
| > I doubt that actually
|
| You can doubt that. But I have done it. I know that it
| works. CL-USER 63 > (defun foo (a)
| (print 'hey) (print a)) FOO CL-USER
| 64 > (foo 'jack) HEY JACK
| JACK CL-USER 65 > (function-lambda-
| expression 'foo) (LAMBDA (A) (DECLARE
| (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 81D03EFF03>))
| (DECLARE (LAMBDA-NAME FOO)) (PRINT (QUOTE HEY)) (PRINT
| A)) NIL FOO CL-USER 66 >
| (fifth *) (PRINT (QUOTE HEY)) CL-
| USER 67 > (setf (fifth (function-lambda-expression 'foo))
| '(print 'hello)) (PRINT (QUOTE HELLO))
| CL-USER 68 > (foo 'jack) HELLO JACK
| JACK
|
| > E.g. both CLISP and guile error out if I try `(car
| (lambda (a b) (+ a b)))`.
|
| Sure, but in CLISP the function is still a list
| internally. An interpreted function is a record, which
| stores the code as a list internally. The internally
| stored list is interpreted.
|
| Python compiles the code to byte code. CLISP has both a
| list-based interpreter and a byte code interpreter.
| mppm wrote:
| Mmh. Like I said, I'm not familiar with LispWorks, so
| take this with a grain of salt, but to me it looks like
| the system is just retrieving the original source
| expression that it keeps around in addition to the
| executable representation. But this is ultimately a
| question of implementation. My original point was that in
| Picolisp runtime-constructed lists are directly
| executable, without any processing. An unroller function
| that takes an action `foo` and a runtime number, e.g. 3,
| would return `'(() (foo) (foo) (foo))` and that would be
| it. In other Lisps you would first build the equivalent
| of this list and then pass it to `eval` to make it
| actually executable. Whether this step is expensive or
| not depends on the system. E.g. efficient closures
| require scanning the containing scopes and creating
| minimal state objects. Just storing the parent
| environment pointer would be super-inefficient and would
| prevent the entire environment from being garbage-
| collected, hence my claim that dynamic scope is the only
| thing that really makes sense in a direct-interpreted
| lisp, and that the presence of lexical scope implies some
| nontrivial processing before execution, though not
| necessarily as extensive as what would usually be called
| compilation.
|
| Edit: lexical analysis -> lexical scope
| lispm wrote:
| CL-USER 100 > (defun unroll (n exp) `(lambda () ,(loop
| repeat n collect exp))) UNROLL CL-
| USER 101 > (unroll 3 '(foo)) (LAMBDA NIL ((FOO)
| (FOO) (FOO))) CL-USER 102 > (eval *)
| #<anonymous interpreted function 8020000EC9>
| CL-USER 103 > (describe *) #<anonymous
| interpreted function 8020000EC9> is a TYPE::INTERPRETED-
| FUNCTION CODE (LAMBDA NIL ((FOO) (FOO)
| (FOO)))
|
| As you can see, the thing is basically the same as a cons
| cell with two entries the type and the code:
| (TYPE::INTERPRETED-FUNCTION . (LAMBDA NIL ((FOO) (FOO)
| (FOO))))
|
| The above Lisp implementation does not use a cons cell,
| but a different type mechanism to easily and reliably
| identify the runtime type.
|
| I picolisp this is hardwired into the interpreter. The
| interpreter will also need to check every time at
| runtime, if the list structure is actually a function and
| what kind of function it is.
|
| In above Lisp, the type of the function is encoded during
| EVAL and the check for the type is then a type tag check.
|
| for this example here, using the LispWorks
| implementation, it also makes no difference for EVAL if
| it is a function with 10 or with 100000 subforms. The
| execution time is small. No special processing of the
| list of subforms takes place. For example the code is not
| compiled, not converted to byte code, not converted to
| another representation. CL-USER 111 >
| (let ((f (unroll 10 '(foo)))) (time (eval f)))
| Timing the evaluation of (EVAL F) User time
| = 0.000 System time = 0.000
| Elapsed time = 0.000 Allocation = 184
| bytes 0 Page faults GC time =
| 0.000 #<anonymous interpreted function
| 8020001DA9> CL-USER 112 > (let ((f (unroll
| 100000 '(foo)))) (time (eval f))) Timing the
| evaluation of (EVAL F) User time =
| 0.000 System time = 0.000 Elapsed
| time = 0.000 Allocation = 184 bytes
| 0 Page faults GC time = 0.000
| #<anonymous interpreted function 8020000839>
| CL-USER 113 > (defun unroll (n exp) `(lambda () ,(loop
| repeat n collect exp))) UNROLL
| mppm wrote:
| I stand corrected, thank you :)
|
| I always thought that `eval` in CL was an un-idiomatic
| and fairly expensive operation, even for code that is not
| compiled. You learn something every day...
| lispm wrote:
| A Common Lisp implementation may implement EVAL by
| calling the compiler. That would be more expensive.
| Several Common Lisp implementation use EVAL to create an
| interpreted function and then the user can call COMPILE
| to compile these.
| kazinator wrote:
| But the behavior changed accordingly when lispm mutated
| the source expression.
|
| So if there is another form that is actually being used
| for the execution, the change in source must have been
| detected and propagated to that other form.
|
| Anyway, that situation looks like true blue interpreted
| functions. There is nested list source you can tweak, and
| the tweaks somehow go into effect.
| kazinator wrote:
| > (car (lambda (a b) (+ a b)))
|
| An interpreted function in a Common Lisp cannot literally
| just be a lambda expression list, because that would not
| satisfy the type system. It has to be of type _function_
| and a _function_ is not a subtype of _list_.
|
| What happens is that there is some container object which
| says "I'm an (interpreted) function", which has slots
| that hold the raw source code. It might not be a lambda
| form; for instance, the original lambda might be
| destructured into parameters and body that are separately
| held.
|
| There is some API by which the interpreter gets to those
| pieces and then it's just recursing over the nested
| lists.
|
| > _Another good test is to construct a function at
| runtime and try to call it._
|
| Common Lisp doesn't provide a standard API for
| constructing an interpreted function (or even make
| provisions for the existence of such a thing). Lisps that
| have interpreted functions may expose a way for
| application code to construct them without having to
| _eval_ a _lambda_ expression.
|
| It's just a matter of constructing that aforementioned
| container object and stuffing it with the code piece or
| pieces. If that is possible then that object is something
| you can call.
|
| When you call that function, _eval_ ends up used anyway.
| cess11 wrote:
| There's also the ultra-minimalist part. Picolisp has like
| one data structure, it's two cells with pointers, and
| that's it. Maybe symbols are implemented in some other way,
| I'm not sure, but pretty much everything is based around
| that.
|
| Portability is not a concern. Either you run something 64
| bit POSIX or you aren't going to use Picolisp (except if
| you get your hands on an old 32 bit build). I think it's
| usually tested by a user on OpenBSD but outside of Debian
| you're basically on your own.
|
| There are like three basic data types. Fixnums, symbols and
| the linked list. If you do something similar to what you're
| showing from SBCL (or LispWorks, didn't read closely enough
| at first) it'll look pretty much like it does in source.
| $ pil + : (de myfun ()(prinl "yo")(prinl "world"))
| : (cdr myfun) -> ((prinl "yo") (prinl "world"))
| : myfun -> (NIL ((prinl "hey")) (prinl "world"))
| : (myfun) yo world -> "world"
| : (set (cdr myfun) '(prinl "hey")) -> (prinl
| "hey") : myfun -> (NIL (prinl "hey")
| (prinl "world")) : (myfun) hey
| world -> "world"
|
| Hijacking the 'de mechanism is not something you'll do
| often, but looking at definitions like this you'll do a
| lot, and from time to time navigate it with list browsing
| functions.
|
| It boils down to some very simple interpreter behaviours,
| and after some time surprises become quite rare. I find it
| takes off quite a bit of cognitive load when solving non-
| trivial scripting tasks compared to e.g. bash or Python.
| Especially since 'fork and 'in/'out are so easy to work
| with, with the former you just pass in an executable list,
| '((V1 V2 Vn)(code 'here)(bye)), with the latter you get a
| direct no-hassle connection to POSIX pipes.
| lispm wrote:
| LispWorks, I change the interpreted code:
| CL-USER 63 > (defun foo (a) (print 'hey) (print a))
| FOO CL-USER 64 > (foo 'jack)
| HEY JACK JACK CL-USER 65 >
| (function-lambda-expression 'foo) (LAMBDA (A)
| (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0}
| 81D03EFF03>)) (DECLARE (LAMBDA-NAME FOO)) (PRINT (QUOTE
| HEY)) (PRINT A)) NIL FOO CL-
| USER 66 > (fifth *) (PRINT (QUOTE HEY))
| CL-USER 67 > (setf (fifth (function-lambda-expression
| 'foo)) '(print 'hello)) (PRINT (QUOTE HELLO))
| CL-USER 68 > (foo 'jack) HELLO JACK
| JACK
| cess11 wrote:
| Is the first line in the body executable?
| (LAMBDA (A) (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ
| Hash Table{0} 81D03EFF03>)) (DECLARE (LAMBDA-
| NAME FOO)) (PRINT (QUOTE HEY)) (PRINT
| A))
|
| If not, one would probably need to do a bit of fiddling
| to tear away the function from the symbol if one should
| feel a sudden urge to do so.
|
| Perhaps similar to this: : (de myfun
| (D)(prinl "heyo") (prinl D)) -> myfun :
| myfun -> ((D) (prinl "heyo") (prinl D)) :
| (mapcar '((D) (prinl "heyo") (prinl D)) '("world"))
| heyo world -> ("world") : (mapcar
| '(NIL (prinl "hey")) '(lel)) hey ->
| ("hey") : (car myfun) -> (D) :
| (cdr myfun) -> ((prinl "heyo") (prinl D))
| : (cons (car myfun) (cdr myfun)) -> ((D) (prinl
| "heyo") (prinl D)) : (mapcar (cons (car myfun)
| (cdr myfun)) '("world")) heyo world
| -> ("world")
|
| Mostly I use this to get at an implementation so I can
| test it or a portion of it against some particular value,
| or just to see how something works. Most builtins are
| implemented in assembler and their symbols only return a
| pointer, but for example 'doc is implemented in Picolisp:
| : doc -> ((Sym Browser) (raw T) (call (or Browser
| (sys "BROWSER") "w3m") (pack "file:" (and (= 47 (char
| (path "@"))) "//") (path (if (get Sym 'doc) (pack @ "#"
| Sym) "@doc/ref.html")))) (raw NIL)) : de
| -> 270351 : macro -> ("Prg" (run (fill
| "Prg")))
|
| I really like the Picolisp 'match (https://software-
| lab.de/doc/refM.html#match ) function. What's the easiest
| way to do the same in Common Lisp? If it's not obvious
| from the examples there, it can also be used with
| character lists, i.e. transient symbols, i.e. strings,
| chopped up into a list of UTF-8 characters. It's similar
| to unification in logic programming, which is something
| Picolisp supports.
| lispm wrote:
| LispWorks: CL-USER 130 > (defun myfun
| (d) (print "heyo") (print d)) MYFUN
| CL-USER 131 > (let ((source (function-lambda-expression
| #'myfun))) (mapcar (eval (list*
| 'lambda
| (second source)
| (nthcdr 4 source)))
| '("world"))) "heyo" "world"
| ("world")
|
| Above can in some ways done in many Lisp implementations.
| It's in this form simply not widely used. For most
| applications it's more interesting to use macros to
| manipulate code, which then can be compiled to efficient
| code.
|
| Pattern matching is much implemented in Lisp.
|
| I had adopted this code for a pattern matcher from a book
| (LISP, Winston/Horn), probably >30 years ago:
| CL-USER 115 > (pmatch:match '(#$a is #$b) '(this is a
| test)) ((B (A TEST)) (A (THIS))) CL-
| USER 116 > (pmatch:match '(#$X (d #$Y) #$Z) '((a b c) (d
| (e f) g) h i)) ((Z (H I)) (Y ((E F) G)) (X ((A B
| C))))
|
| Not to say, that Picolisp isn't great for you, but it is
| not the only language where lists can be manipulated.
| ngcc_hk wrote:
| Doing one using lua and love2d and hence found some difference or
| things to ponder
|
| - use ds and discrete event simulation instead of the usual dt
| (and continuous ...)? Why? Seems all game engine offer dt ...
|
| - shall the track be one way and if two way traffic has to use 2
| tracks? Otherwise if >1 train or train-group ... crash ? And if
| there are junction ... route and timing would be issues otherwise
| crash ?
|
| - picolisp vs fennel ?
| cess11 wrote:
| "- picolisp vs fennel ?"
|
| Not really comparable. Picolisp is really small, really simple,
| and that makes it kind of weird. It has fexprs rather than
| sexprs, there are no seatbelts (as in you will segfault when
| reaching out of bounds), the numbers are fixnums, the database
| is object oriented and you query it with logic programming, and
| so on.
|
| As far as I know, no one has used it for 2D game scripting. If
| you want to be the first you probably can be, but it will be
| quite an adventure compared to Fennel.
| isr wrote:
| I think you meant "fexprs instead of macros".
|
| Picolisp does have a function named 'macro', but it's not the
| same thing (it's more like 'string map' from tcl).
|
| There's a lot if interesting stuff in picolisp, but the one
| thing I found unbearably ugly was it's use of strings
| ("transient symbols") as a kind of lexically scoped symbol.
| cess11 wrote:
| Right, yes. In some sense it's not sexpr as usual either,
| the evaluation strategy gives it a rather different feel
| than one might be used to from other parens syntaxed
| languages.
|
| 'macro can be used to control evaluation, https://software-
| lab.de/doc/refM.html#macro , but there is no compilation in
| Picolisp, hence no compile time macros.
|
| Don't see the problem with that. How is it different from
| using strings without quotation marks as global symbols?
| isr wrote:
| Hmm, when you put it like that, my complaint does seem
| trite.
|
| It was just one of those things which bugged me ...
| W-Stool wrote:
| No one interested in railroad simulations running Run8 here?
| rob74 wrote:
| > _However, for a comprehensive track network, a simple linked
| list of track elements is insufficient. It must be possible to
| traverse it in both directions and accommodate switches
| (turnouts) and loops. Loops present a particular challenge._
|
| Fun fact: there is at least one track network (the Munich U-Bahn)
| that avoids loops and interconnecting lines "the wrong way
| around", so trains always face the same way (https://www-u--bahn
| --muenchen-de.translate.goog/fahrzeuge/?_...). Because of this,
| two-carriage trainsets have a "north carriage" and "south
| carriage", and the newer six-carriage trainsets have north and
| south end cars plus four middle cars. Of course, not all tracks
| run north-south, but the name is taken from the way the carriages
| point in the first (and at the moment still the only) maintenance
| yard in Frottmaning.
| AstroJetson wrote:
| Late to this conversation but a favorite game with my children
| was Lego Loco
|
| https://en.wikipedia.org/wiki/Lego_Loco
|
| You could build little villages with train layouts. The big thing
| for them, is you could put tunnels that would connect to another
| user. It let you send trains and the mail card had "post cards"
| you could send messages.
|
| I miss it.
| interloxia wrote:
| >With some (considerable) effort, you could even replace the
| ASCII representation by something more visually appealing
|
| Connect it to OSRD!
|
| Open-Source Railway Designer (posted a few days ago)
| https://news.ycombinator.com/item?id=40733705
| greathones wrote:
| Any link to blogs/materials about programming train simulation
| (like in openttd)?
___________________________________________________________________
(page generated 2024-06-24 23:01 UTC)