« first day (448 days earlier)      last day (2253 days later) » 

6:10 AM
#tio alias view
 
@Adám
Command Aliases:
⍞← -> #TIO do apl-dyalog {0::⎕←⊃⎕DM⋄⎕←%args%}⍬
⎕← -> #TIO apl {0::⎕←⊃⎕DM⋄⎕←%args%}(⎕NS⍬).⍎'⎕CY''salt''⋄⎕SE.UCMD''←box on -f=on -t=tree''⊣enableSALT⋄⍬'

Language Aliases:
apl-dyalog -> [apl]

Message Aliases:
)help -> %handle% [Dyalog APL Language Elements](help.dyalog.com/16.0/Content/Language/Introduction/…)
)ref -> %handle% [Dyalog APL Reference Card](docs.dyalog.com/16.0/ReferenceCard.pdf)
)about -> %handle% You can evaluate an APL expression by typing it into chat prefixed by ⍞←. Use ⎕← instead for boxed display and multi-line r
 
#tio alias command ⋄ #TIO apl {0::⎕←⊃⎕DM⋄%args%}(⎕NS⍬).⍎'⎕CY''salt''⋄⎕SE.UCMD''←box on -f=on -t=tree''⊣enableSALT⋄⍬'
 
@Adám Added alias for ⋄
 
⋄ f←∘.≠⍨ ⋄ f 0 1
 
@Adám

Rebuilding user command cache... done

Real time: 1.379 s
User time: 1.094 s
Sys. time: 0.052 s
CPU share: 83.12 %
Exit code: 0
 
6:13 AM
⋄ f←∘.≠⍨ ⋄ ⎕←f 0 1
 
@Adám
0 1
1 0
 
@Rinav Welcome. Interested in APL?
#tio alias message )about %handle% You can evaluate a single line of APL by typing it into chat prefixed by ⍞←. Use ⎕← instead for boxed display and multi-line results and use ⋄ instead to silence the first statement. Do not use markdown, but fixed-width (4 initial spaces) is fine. Commands: )lb for language bar, )help for table of language elements, )docs for full documentation, )ref for PDF reference card, )idioms for idiom list.
 
@Adám Added alias for )about
 
)about
 
@Adám You can evaluate a single line of APL by typing it into chat prefixed by ⍞←. Use ⎕← instead for boxed display and multi-line results and use ⋄ instead to silence the first statement. Do not use markdown, but fixed-width (4 initial spaces) is fine. Commands: )lb for language bar, )help for table of language elements, )docs for full documentation, )ref for PDF reference card, )idioms for idiom list.
 
6:16 AM
#tio alias view
 
@Adám
Command Aliases:
⍞← -> #TIO do apl-dyalog {0::⎕←⊃⎕DM⋄⎕←%args%}⍬
⎕← -> #TIO apl {0::⎕←⊃⎕DM⋄⎕←%args%}(⎕NS⍬).⍎'⎕CY''salt''⋄⎕SE.UCMD''←box on -f=on -t=tree''⊣enableSALT⋄⍬'
⋄ -> #TIO apl {0::⎕←⊃⎕DM⋄%args%}(⎕NS⍬).⍎'⎕CY''salt''⋄⎕SE.UCMD''←box on -f=on -t=tree''⊣enableSALT⋄⍬'

Language Aliases:
apl-dyalog -> [apl]

Message Aliases:
)help -> %handle% [Dyalog APL Language Elements](help.dyalog.com/16.0/Content/Language/Introduction/…)
)ref -> %handle% [Dyalog APL Reference Card](docs.dyalog.com/16.0/ReferenceCard.pdf)
 
 
4 hours later…
10:05 AM
If you haven't had a play with Dyalog APL at TIO (Try It Online) then take a look at Leigh Mercer's mathematical limerick expressed in APL https://tio.run/##TU8xbsJAEOx5xdTYQOwYoRQUKGnoKPKBI7c2J9l35M62ZB5AEQmUJqJKlUf4Af6JPwJrAyJTzUozOzNim45kJVKTnBm23X97kyBEEEUIn4D28IsFpNmR9iGQWOMcEy35cB/Gkj@wHpuem1M0bOoQPTrXKi0c8o0lQq4y6jjBfRbCEqwxOUyM2BSWHzQ1f5jhgc7/pkolSWJdwVHJ8degaXMKgv@6PidW5T2G0pt6/jIM0X79oT3@vL6z9TZn6aCVvneR/RjNfQTWKkfGm8YX
 
 
5 hours later…
3:29 PM
That's blown the diet!!! Thanks for the gift @BPBAPLer - 4 different Reese's Peanut Butter Cups - and you have to try one of each to see which one you like best :-) https://twitter.com/Jasey_Wasey/status/986610772854886400
 
 
2 hours later…
5:14 PM
What will we be talking about in today's lesson?
 
@J.Sallé My plan is multi-threading with APL threads.
 
Oh yeah, we talked about it last week. That's exciting!
 
5:30 PM
Welcome to the APL Cultivation!
Today's subject is multi-threading using APL threads.
Tomorrow, CXO Morten Kromberg will be presenting multi-threading using OS threads on dyalog.tv.
OK, so what is an APL thread?
Dyalog APL is allotted processor time by the operating system, and normally pretty much all that processor time is dedicated to computing whatever expressions the user (or his program) throws at APL.
The session will be locked while APL computes, and then the result (if any) is displayed.
However, sometimes you want to have some program running in the background while you do other stuff, or you want to run multiple programs at the same time, each e.g. monitoring various events happening in the environment.
You could also have two simultaneous GUI programs so that the end-user may interact with each one independently.
 
Fair enough
 
For all this, Dyalog APL provides APL threads, where the interpreter makes sure to slice the available processor time between your tasks, much in the same way as the OS slices processor time to the various programs running on the computer.
The mechanism is quite neatly integrated with the language in the form of a monadic operator &. You may recognise this symbol from UNIX-like OSs where the addition of a & at the end of the command line executes the command in a separate thread.
 
Aaaah so that's what "spawn" means? Spawn a new thread?
 
Yes. and it isn't a coincidence. Indeed, as a monadic operator, it goes to the right of the function you are calling: foo&
So foo& is a derived function which is just like foo but executes in a separate thread. Just like any other derived function you can assign this to a separate name, etc.
 
How would that work on a Dfn?
 
5:41 PM
However, remember that you can't apply an operator to a niladic function, so if want to call a niladic function with &, you need to enclose it in a non-niladic function, and give it a dummy argument: {niladic}&'dummy'
 
I'd just do dfn←{stuff}& or I'd call it as dfn&?
 
@Adám then how do you apply e.g. ...oh I see, it's not very properly worded
 
ah okay, it needs an argument
 
@J.Sallé {stuff}&arg or fn←{stuff}& ⋄ fn arg or fn←{stuff} ⋄ fn&arg
OK, enough theory. Let's see this in action!
Since the bot can't output things as they happen, we'll need some tricks to see the effect.
⋄ {⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&2 ⋄ ⎕DL 1 ⋄ ⎕←'    Unrelated stuff'
 
@Adám
Started waiting 2 s
Elapsed: 2.003664 s
 
5:45 PM
Hm, that was supposed to work.
⋄ {⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&2 ⋄ ⎕←' Unrelated stuff' ⊣ ⎕DL 1
 
@Adám
Started waiting 2 s
Elapsed: 2.003645 s
 
@Adám that on the front though...
is it the new way for the bot?
 
Oh well, we'll use TIO then.
@EriktheOutgolfer New feature to suppress output of the first expression.
The result was:
Started waiting 2 s
    Unrelated stuff
Elapsed: 2.006793 s
So what happened here?
{⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&2 spun off a thread with the dfn outputting the starting message, then waiting 2 secs, then outputting the elapsed message.
Meanwhile, execution continued with waiting a second, and then outputting the unrelated stuff.
@all Clear to everyone?
 
Perfectly
 
OK, let's up the complexity a bit.
 
5:51 PM
@Adám Imagine that on a real parallel processor...
 
@EriktheOutgolfer Yup, you can see that in tomorrows show.
 
@Adám Why is the ⎕DL 1 nessercary? (I see that it can be ⎕DL something-small aswell)
 
@H.PWiz It could be smaller. Just wanted to make it perfectly clear what happens, and if you try this in your local APL session, the visual effect is neater.
So, since fn& is just another (derived) function, you can of course apply it to anything you want, just like any other function.
 
Right, but why must you wait at all
 
@H.PWiz To give the dfn time to act.
 
5:54 PM
@Adám just tried it on my repl, it's indeed neater
 
This means that fn&¨ spawns one thread for each element of the argument.
Shall we give the bot one more chance?
⋄ {⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&¨0.6 1.2 1.8 ⋄ ⎕DL 1 ⋄ ⎕←'    Unrelated stuff'
 
@Adám
Started waiting 0.6 s
Started waiting 1.8 s
Started waiting 1.2 s
Elapsed: 0.599788 s
Elapsed: 1.206431 s
Elapsed: 1.803153 s
 
@DyalogAPL Nah.
 
It's trying its hardest D:
 
yeah, doesn't seem like it
 
5:58 PM
⎕←{⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&¨0.6 1.2 1.8 ⋄ ⎕DL 1 ⋄ ⎕←'    Unrelated stuff'
 
@Adám
Started waiting 0.6 s
1 2 3
Started waiting 1.8 s
Started waiting 1.2 s
Elapsed: 0.599973 s
Elapsed: 1.202438 s
Elapsed: 1.803203 s
 
I mean, it literally ditches a part of the output off, and it's the same output as the rest!
 
It's funny how it did 0.6, then 1.8 and 1.2. The same happened in the REPL
 
Ah, the Morten just made me aware why it fails.
 
@Adám I'm still a little confused. If I use some timeconsuming function to "give the dfn time to act", It still happens before the dfn:
https://tio.run/##SyzI0U2pTMzJT///v/pR39RHbRPUg0sSi0pSUxTKEzNLMvPS1XUe9W7VUS9WV3jU3aIAVeOak1hQnJpipa6jARRx8VEAqtEEKapVM9Az5IKo0tAwfNQx4/B0zUe9iw0NIEATyPj/HwA
 
6:01 PM
the Morten, one and only!
 
@H.PWiz The computation was so fast that it finished before the dfn even got to output.
 
apparently it's the CXO :P
 
Here we go:
 
expectation intensifies
 
⋄ {⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&¨0.6 1.2 1.8 ⋄ ⎕DL 1 ⋄ ⎕←'    Unrelated stuff' ⋄ ⎕DL 1
 
6:04 PM
@Adám
Started waiting 0.6 s
Started waiting 1.8 s
Started waiting 1.2 s
Elapsed: 0.599804 s
Elapsed: 1.202338 s
Elapsed: 1.802879 s
 
⎕←{⎕←'Started waiting',⍵,'s' ⋄ ⎕←'Elapsed:',(⎕DL ⍵),'s'}&¨0.6 1.2 1.8 ⋄ ⎕DL 1 ⋄ ⎕←'    Unrelated stuff' ⋄ ⎕DL 1
 
RIP Dyalog bot
 
@Adám
Started waiting 0.6 s
1 2 3
Started waiting 1.8 s
Started waiting 1.2 s
Elapsed: 0.599918 s
Elapsed: 1.202645 s
Elapsed: 1.803359 s
 
@DyalogAPL OK, forget it!
 
@Adám I didn't mean to delete my comment, but the execution time on that TIO link is >1s, while the dfn only takes 0.1s of that time. I think 1s should be long enough
 
6:06 PM
@H.PWiz One minute, Morten is taking a look.
 
Thanks
 
@H.PWiz Remember that APL has to slice the processor time. It only does so at certain times, not inside single statements.
 
So it can do so during waits but not while executing something?
 
Are you doing job control?
 
@H.PWiz It does so between lines, but not more often than a certain setting allows it to.
Btw, this means that you can ensure that multiple statements are not interrupted by putting them on the same line with diamonds instead of line breaks.
It also switches threads during ⎕DL, so this also answers why we need ⎕DLs in our examples.
@Pavel What is job control?
 
6:14 PM
I think I pretty much undestand this, but thread stuff is still a somewhat unintuitive
I also suspect it has something to do with lines in TIO being executed immeadiately after one another (unlike in a REPL). Which I can't test right now
 
@H.PWiz Yeah, what you see is simply an artefact of using artificial examples and uninterruptible statements.
 
@Adám I take it more like the two statements were running against each other in a marathon and the second finished entirely before the first even got to the first step :P
 
@EriktheOutgolfer Yes.
OK, now I'm going to spawn a thread which increments a global variable once a second for four seconds: {r←⍵ ⊣ N+←1 ⊣ ⎕DL ⍵}⍣4&1
 
I got pinged in this room but I can't find the message that pinged me ..?
 
when did you get "pinged"? messages aren't the only thing that makes a ping sound
 
6:19 PM
6 mins ago, by Adám
@Pavel What is job control?
That one?
 
The we will use f←{r←⍵ ⊣ ⎕←⍺,N ⊣ ⎕DL ⍵} which takes a label as left arg and an waiting time as right arg.
 
@H.PWiz Looks like it, but I can't actually see the message. Only in the transcript.
Weird
Job control = manipulating background processes
 
uh, I can?
 
And I.. can't.
 
Calling 'ABC' f⍣5&¨ 0.2 0.6 0.8 will spawn three fs, each looping 5 times, each waiting various times in each of its loops. and outputting its label and the global variable result in each loop.
 
6:21 PM
It just isn't there.
 
@Pavel No, the APL threads are treated equally (if that's what you are asking).
 
It just means suspending/resuming/waiting for/killing them
 
@Pavel Well, you as the APL programmer can do that. We'll get there.
So in this example you can see concurrent threads interacting with each other.
@all Clear?
 
makes sense
(mostly)
Haven't caught up on the entire lesson yet
 
Yup, perfectly.
 
6:26 PM
OK, let's take an example where that threads don't just blindly do something, but rather make a decision to do something or continue based on the circumstances at the time.
We'll use f←{0=⍺|N ⊣ ⎕DL ⍵:r←⍵ ⊣ ⎕←⍺,'divides',N ⋄ r←⍵}
So it waits a bit, then checks whether the global N is divisible by its left arg, and if so, outputs a message, otherwise it just quits. The r← is to make the function shy. We return to make apply to the same argument again.
OK, now let's address job control.
So in order to handle threads, we need a way to identify them.
fn& arg doesn't return the result of fn arg as that value isn't available yet.
Rather it returns an APL thread number, shyly.
We can capture this number and use it with various system functions to act upon the threads.
⍞←'thread number',0.1{⎕←1+⍵ ⊣ ⎕DL ⍺}⍣10&0
 
@Adám thread number 1
 
⎕←'thread number',0.1{⎕←1+⍵ ⊣ ⎕DL ⍺}⍣10&0 ⋄ ⎕←'thread number',0.1{⎕←1+⍵ ⊣ ⎕DL ⍺}⍣10&0
 
@Adám
thread number 1
thread number 2
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
 
OK, now compare:
⋄ ⎕←'thread number',0.1{⎕←1+⍵ ⊣ ⎕DL ⍺}⍣10&0
 
@Adám
thread number 1
1
2
3
4
5
6
7
8
9
10
 
6:37 PM
And:
⋄ t←0.1{⎕←1+⍵ ⊣ ⎕DL ⍺}⍣10&0 ⋄ ⎕DL 0.5 ⋄ ⎕TKILL t
 
@Adám
1
2
3
4
5
6
7
8
9
10
 
@DyalogAPL Stupid bot.
5
So here you can see that ⎕TKILL killed the thread before it had time to print all 10 numbers.
 
Useful
 
⎕TNUMS gives a list of currently running thread numbers. 0 is the main APL thread, so it is always on the list, and you can't kill it.
 
Do these threads map 1:1 to OS threads?
 
6:42 PM
So now we'll start a thread which runs for a second, outputting numbers every tenth of a second, but after 0.7 seconds, we'll check whether it has finished (i.e. isn't on the list) and if it is, we kill it, otherwise we say it has finished: {⍵∊⎕TNUMS:'Timed out'⊣⎕TKILL ⍵ ⋄ 'Finished'}
 
@quartata They aren't OS threads at all
 
@quartata No, they do not. They are all inside a single OS thread — that of the APL process. To learn how to do all this in separate OS threads, watch tomorrow's Webinar.
 
APL has it's own internal scheduler
 
And here is a version which waits long enough for the first thread to finish: Try it online!
What if we want to wait until a thread has finished?
⎕TSYNC takes a thread number and waits for that thread to finish: Try it online!
Remember that & couldn't return the result of the function because that wasn't available yet?
 
⎕TSYNC returns that
 
6:48 PM
⎕TSYNC doesn't return anything...
 
Yes, clearly, ⎕TSYNC is able to get the result, which is then its result: Try it online!
 
Oh my god my internet is currently 1 KiB/s
 
@Adám ah, does the thread need to exist for it to return its argument?
 
Yeah, if the thread has already finished by the time you call ⎕TSYNC, then it doesn't have a value, and you may get a value error if you try to use the result. You'll have to trap that or make sure to call it in time.
 
⋄ t←{4}& ⋄ ⎕←⎕TSYNC t
 
6:51 PM
@Pavel
┌──┴───┐
⎕TSYNC &
     ┌─┘
     {4}
 
._.
 
@DyalogAPL Remember that you need an argument to {4}&.
 
Oh yeah
⋄ t←{4}&⍬ ⋄ ⎕←⎕TSYNC t
 
@Pavel
4
 
There we go
 
6:53 PM
@DyalogAPL Yay, you did it!
 
@DyalogAPL What is this output representing?
 
Naturally, as APL is an array language, you can give ⎕TSYNC an array of thread numbers. The result will have the same shape as the array of thread numbers, with each corresponding element being the result of that thread. ⎕TSYNC will wait for the last necessary thread to finish.
@Pavel You made t into a derived function, and then you used that to create an atop.
@Pavel It has exactly the same form as:
⎕←× +/
 
@Adám
┌┴┐
× /
┌─┘
+
 
Ah, I see
 
This is the signum of the sum. You had the ⎕TSYNC of the spawned four-function.
Synchronising multiple threads: Try it online!
 
6:56 PM
It's nice that TSYNC can just, like, take an array and wait on all of the tasks. In Python you have to use some more uglier functions, like asyncio.gather.
 
I like how bash has wait, which just waits for all your background tasks to finish. EZ.
 
@Pavel ⎕TSYNC ⎕TNUMS.
 
@Adám What does ⎕TSYNC 0 do?
 
@Pavel Guess!
 
Nothing?
Halts?
 
6:59 PM
If 0 is the main task then I shouldn't think so :p
 
APL has automatic detection of deadlocks, i.e. when a set of threads are all awaiting each other so that none can continue. ⎕TSYNC 0 says "wait for the main APL thread to finish", but APL knows that won't happen, so it throws a DEADLOCK exception right away.
Of course you can trap deadlock exceptions and handle them appropriately.
 
Ha. Nice.
 
And ⎕TNUMS contains 0, so ⎕TSYNC ⎕TNUMS throws DEADLOCK, no?
 
@Pavel Ah, yeah, my bad. You'd need ⎕TSYNC ⎕TNUMS~0.
 
Is there a way to await any of an array of tasks, e.g. return the result of the first one to finish?
 
7:02 PM
Also, the current thread is ⎕TID, so you can have a thread await all other threads with ⎕TSYNC ⎕TNUMS~0 ⎕TID.
@quartata There is a way, but then you need to use tokens, which I won't cover today, because…
That's all for today!
 
a cliffhanger
 
@quartata technically we were already 2 minutes late...
 
@Adám thanks for the lesson, it was enlightening
 
Don't forget the webinar tomorrow about using multiple OS threads.
 
@Adám except that, as usual, I won't be able to watch it live, Karate is more of a priority than APL :P
(one hour after the webinar starts)
 
7:09 PM
@EriktheOutgolfer Morten just got back from a 27 km bicycle ride and totally understands.
 
@Adám 2...7...k...m...‽
 
that's indeed a lot of ground
 

« first day (448 days earlier)      last day (2253 days later) »