_rend
Yeah, this is an unfortunate problem in most non-GC languages with implicit destructors. See, e.g., Swift
enum Node {
case none
indirect case next(Node)
}
func example(_ n: Int) {
var head = Node.none
for _ in 0 ..< n {
head = .next(head)
}
}
example(100000)
// Terminated due to signal: SEGMENTATION FAULT (11)
or Rust struct Node {
next: Option<Box<Node>>
}
impl Node {
fn new(next: Node) -> Self {
Node { next: Some(Box::new(next)) }
}
}
fn example(n: isize) {
let mut head = Node { next: None };
for _ in 0 .. n {
head = Node::new(head);
}
}
fn main() {
example(100000)
// thread 'main' (24909708) has overflowed its stack
// fatal runtime error: stack overflow, aborting
// Terminated due to signal: ABORT TRAP (6)
}
I'm not aware of languages (esp. systems languages) that can either guarantee some form of tail-call optimization for destructors, or that make an attempt to recognize recursive data structures and accommodate them in some way. (The latter is likely a losing proposition, anyway.)One way to represent side effects in a purely functional language is to model them as if they aren't side effects, by representing them as state changes in the "outside world". You don't need to grok the specifics of this, but the definition of the `IO` monad is:
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
i.e., it's a "pure" transformation of the "real world".This allows you to define a "box" called `IO` that represents a computation that can perform a side-effect (by affecting the "real world"), then returning a value.
The real trick to this is that the "box" is entirely opaque to you: unlike a list or a `Maybe` where you know how to reach in and pull values _out_ (e.g., `head`, `last`, `fromJust`, etc.), `IO` doesn't allow you to do this*. Once you have something inside of an `IO` box, it's stuck there.
This means that you can separate the "impure" world from the "pure" world: you can't perform side effects arbitrarily — you're can only do so in an `IO` context that's intentionally "viral".
The functor/applicative/monad rules just make `IO` easier to use and consume:
1. `Functor` allows you to "map" over the results of a computation
2. `Applicative` allows you to chain computations together in order so side effects happen in sequence
3. `Monad` makes it easier to repeatedly chain computations within a single `IO` context (so if you need to perform repeated side effects, you can "stay" in the outer context — `IO a` instead of `IO (IO (IO (IO (... (IO a)))))`)
This is just one way to represent side effects, and the monad rules are only really needed to make this representation ergonomic to actually use.(*There is technically a way to "escape" the `IO` monad called `unsafePerformIO`, but you basically never need to use this. If you find yourself reaching for it, don't.)
[1, 2, 3] :: [Int]
Here, the "box" is a list, and inside of it are the values 1, 2, and 3.As you know, `map` is an operation that converts the values inside of the box into other values; for example, adding 1 to every element:
[1, 2, 3] :: [Int]
| | | (+ 1)
v v v
[2, 3, 4] :: [Int]
But the operation you perform with `map` doesn't need to keep the values of the same type: [ 1, 2, 3 ] :: [Int]
| | | (show)
v v v
["1", "2", "3"] :: [String]
The operation can also produce new boxes! Since `String` is actually itself a list (`[Char]`), the result above is the same as [ 1, 2, 3 ] :: [Int]
| | | (show)
v v v
[['1'], ['2'], ['3']] :: [[Char]]
In some cases, you might want to "flatten" this box-of-boxes together. In some languages this operation is called "flatten"; for lists in Haskell, it's called `concat` [['1'], ['2'], ['3']] :: [[Char]]
| | | (concat)
v v v
[ '1', '2', '3' ] :: [Char]
This example isn't terribly motivating, but you can see when you have deeper lists-of-lists how this might be handy: [[1,2,3], [4,5,6], [7,8,9]] :: [[Int]]
| | | (concat)
v v v
[1, 2, 3, 4, 5, 6, 7, 8, 9] :: [Int]
Here, we took a collection of boxes (`[[Int]]`) and combined them in order (sequentially) to produce a new box (`[Int]`).What other languages call `flatMap` is just a `map` operation followed by a `flatten` operation. Very roughly, `Functor` gives you "map" (`map`), `Applicative` gives you "flatten" (`concat`), and `Monad` gives you "flatMap" (`concatMap`).
The power of these comes from considering different types of "boxes". `Maybe`, for example, works almost like a list that can contain up to 1 element, and its operations behave pretty much identically. Other types are interesting because how you define their "box-ness" can lead to interesting/useful results. It can be tough to envision how, e.g., a function could look like a "box", but it turns out that you can define rules for it that make it useful. (What does "map" look like for a function? Well, it turns out that mapping a function over another function is already just... function composition!)
You can go a lot deeper into these definitions, and it helps to look at some implementations to grok them better, but the core concepts themselves are not very complicated. The "magic" is in how you define the "boxes".
"Functor", "Applicative", and "Monad" are all just generalizations of the concept of `map` and `flatMap`.
1. Something is a "Functor" if you know how to call `map` on it, nothing more. "I can take a box of things and turn it into a box of other things, 1-to-1". On lists, for example, this is just `map` itself
2. Something is an "Applicative" if you know how to call `map` on it, but also know how to take a non-boxed value and put it in a box, and also know how to combine boxes in order
3. Something is a "Monad" if you know how to do all of the above, but also know how to call `flatMap` on it, nothing more. "I can take a box of things, turn each thing into a new box, and then combine them all in order". On lists, for example this is just `concatMap`
There's nothing really more complex to it, besides how you squint at various things (like functions) to fit them into the concept of `map` and `flatMap`.To answer your questions more directly:
1. Monads themselves are neither necessary nor sufficient to perform side effects in Haskell; they don't directly enable the effects, but they *do* help place guardrails on the actual unsafe, low-level code which *can* perform the effects, safely and in an ergonomic and composable way
2. Yes, "Monad" is just a name for a recurring way to approach a problem. Like in most math and programming, a certain repeating pattern was noticed, and given a name. Because of the math origin of the term, you get "Monad" instead of "flat-mappable"
3. Like any other tool, you reach for a monad when you have a monad-shaped problem. They're just one (powerful) tool for solving certain problemsIf you're taking feedback, I've been a >decade-long user of LaunchBar, and I've yet to find another launcher that handles my most common actions quite as well (except maybe Alfred):
1. I launch a ton of URLs directly from LaunchBar, and it's a killer feature for me to be able to start typing a URL (not intending to match anything) and as soon as I type a period, LaunchBar converts the search to a URL (and inserts 'https://' and '.com'). e.g., if I type "abc.", LaunchBar will expand to "https://abc.com" with the ".com" highlighted for replacement (and hitting Return will open the URL immediately). Right now, if I want to do the same with Tuna and my default mode is Fuzzy Mode, I believe I need to hit '"' to enter Text Mode, type the URL, hit Tab, then search for the "Open URL" action (which also won't recognize a "bare" URL without the scheme, so won't show up for, e.g., "abc.com") — but happy to be wrong! I think it'd be swell if it were possible to configure Tuna to, on '.', convert into text mode, automatically insert "https://" and ".com", and automatically pre-populate the "Open URL" action so I could just hit Return to confirm and launch
2. I use the inline calculator a lot, and really like the "auto math" switch when typing digits (and really like the carve-out for 1Password, where typing '1' will show 1Password in fuzzy search instead of switching to the calculator); switching to text mode automatically on numeric input would be really helpful to do the same
3. I have a few custom search templates in LaunchBar I use all the time (several different search engines), and I'm not sure if it's possible to set up something similar directly inside of Tuna yet without writing custom services or an extension
Obviously, this is just how I use LaunchBar, and may not fit in with your vision of Tuna, but figured it might be some helpful food for thought! Thanks for your work on this :)
Even more so, huge kudos for the performance tuning! The app launches instantly, so much so that I was initially a bit shocked. I'd forgotten what it feels like to tap a button and have something functional appear on screen with zero delay. When even the simplest apps have loading screens (and even built-in Settings/Reminders/Notes/Phone/etc. have a delay), this was really refreshing to see! I legitimately force-quit a bunch of apps on my phone to compare and nothing comes close on cold launch. Great work!
> Well, dear reader, this padding is added because the CPU needs memory to be aligned in sets of 4 bytes because it’s optimized in that fashion.
> ...
> Remember: since structs are aligned to 4 bytes, any padding is therefore unnecessary if the size of the struct is a multiple of 4 without the padding.
Individual data types have their own alignment (e.g., `bool`/`char` may be 1, `short` may be 2, `int` may be 4, `long` may be 8, etc.), and the alignment of a compound type (like a struct) defaults to the maximum alignment of its constituent types.
In this article, `struct Monster` has an alignment of 4 because `int` and `float` have an alignment of 4 for the author's configuration. Expanding one of the `int`s to a `long` could increase the alignment to 8 on some CPUs, and removing the `int` and `float` fields would decrease the alignment to 1 for most CPUs.
I'm not sure if this is what you intended, but just to be sure: writing changes to a cloned file doesn't immediately duplicate the entire file again in order to write those changes — they're actually written out-of-line, and the identical blocks are only stored once. From [the docs](^1) posted in a sibling comment:
> Modifications to the data are written elsewhere, and both files continue to share the unmodified blocks. You can use this behavior, for example, to reduce storage space required for document revisions and copies. The figure below shows a file named “My file” and its copy “My file copy” that have two blocks in common and one block that varies between them. On file systems like HFS Plus, they’d each need three on-disk blocks, but on an Apple File System volume, the two common blocks are shared.
[^1]: https://developer.apple.com/documentation/foundation/file_sy...
They've supported Windows and iPad for years, too.
Supreme Court rules ex-presidents have immunity for official acts
But also, native splits/panes and tabs cover 90% of what I really want from a multiplexer, so it's easier for me personally to stick with familiar behavior than to integrate another tool into my workflow just to recreate it.
Unfortunately, it's missing one key feature that keeps me from using it as a daily-driver: it doesn't appear to be possible to attach to an existing session by automatically creating a new tab or pane. iTerm2 has fantastic integration with tmux that allows it to directly create a new tmux tab for every native iTerm2 split or tab, and I was hoping to recreate that with Zellij, outside of iTerm2.
It _is_ possible to open a new tab with the `new-tab` action (or whatever it's called), but unfortunately, there's no way to do that "in the background": one of your open sessions always switches to that new tab when it opens. I don't know if this is a limitation of the session/tab system, but when I dug through the source, I couldn't for the life of me figure out why this was happening.
I did spend some time trying to contribute a flag to allow attaching to existing sessions with a new tab/pane, but the actual architecture in place back then made this very difficult to support without non-trivial refactoring (and at least at the time, Zellij wasn't accepting any major contributions that weren't directly aligned with the roadmap, which I respect: there's only enough time in the day to review random PRs).
I check back periodically; if this is made possible at some point, I'd love to switch to it.
Not all doctors make enough money to not notice it, especially ones earlier in their career.
Regardless, assuming you have two working adults each paying the $15 toll once a day, conservatively, working 250 days a year, that's $7,500/year that has to come from somewhere. I can't imagine an income level (even if it doesn't affect your quality of life) where that's not an insanely frustrating amount to pay… to the MTA of all places. That money getting reinvested into useful infrastructure would be a dream come true!
My dad is an on-call doctor; getting to his hospital by car takes ~15 minutes, but ~60–90 via public transit. His patients don't have the luxury of waiting for him to take the bus. His hospital is outside of this zone, but I imagine that paying $15 every time he got called in would be extraordinarily frustrating.
My mom does work within this zone, also in places not easily reachable by public transit. I suspect that she, like many others, will still commute into Manhattan, park in areas outside of the zone, then take public transit into it — which will increase congestion in those areas. It'll be interesting to watch for the lead-on effects.
I sympathize entirely with the desire to reduce traffic in the city, but man, for people who live far from work and can't easily commute any other way, what a pain.
This is not what functional programming is. Functional programming is a completely different paradigm for writing code, and it _is_ declarative by definition. The Wikipedia definition is pretty decent:
Functional programming is a programming paradigm where programs are constructed by applying and composing functions. It is a declarative programming paradigm in which function definitions are trees of expressions that map values to other values, rather than a sequence of imperative statements which update the running state of the program.
The "Comparison to imperative programming" section[1] offers a decent example of the paradigm, but DOM manipulation in JavaScript is pretty much as imperative as it gets. Having functions is necessary, but not sufficient, for functional programming.[1]: https://en.wikipedia.org/wiki/Functional_programming#Compari...
`pbcopy < ~/.ssh/mykey.pub`
(I use this all the time myself!)
So, for instance, a GPL-licensed git client like GitUp[1] was fine to use, and didn't require clearance. You could totally also install a newer version of Nano if you wanted, too.
But, the rules _were_ somewhat vague and scary-sounding, so many engineers I worked with took the rules to mean "absolutely no GPL software under any circumstances".
What email is actually talking about is the option to bundle Nano _with the OS_, which Apple can't do with GPLv3 software. That's why for years, for example, macOS has had an absolutely ancient version of bash (before the license was updated to GPLv3), and switched to zsh in newer versions of the OS.
My plan is to keep it entirely organic — so far have I've only really sent a few emails around the more meaningful-feeling milestones. (Arriving home, one week, etc.) I think, long-term, I'd prefer to have the amount of content not feel _overwhelming_ when I hand it off; I'm not planning on having this be the primary way to document my kid's life, but as something "extra" to be able to pass off to them.
[Also, being a few weeks in: I'd warn against making any plans for the first few weeks assuming you'll have much brain capacity available to you. I'd say, don't stress yourself out too much if you can avoid it.]
I decided a priori to stick with email, both because I can't imagine email going away anytime soon, and also because sending an email two main benefits: (1) the content is plain text, and should remain easily readable at the destination, and (2) you get automatic "backups" in your own Sent folder.
I also wanted to avoid any free services because there's no guarantee they'll stick around, or in the case of something like Gmail, keep the account around unused. And, although I'm a really happy paying Fastmail customer for many years, a new email address on my account felt a bit _too_ expensive for just this purpose (especially since I'll eventually pay for an actual address for them).
Because I already pay $5/month for a tiny Linode instance for other purposes, I decided to self-host. Because I don't plan on having the account send any emails (the hard part of self-hosting), setup was relatively pain-free with Postfix. It took a bit of configuration and testing, but I now have an email account for my little one hosted on a subdomain which reliably receives email and stores it in plain-text on the server. Because I set things up in the Maildir format, in the future, it should be simple to also run Dovecot to provide an IMAP interface to actually get at the emails, or import them elsewhere.
I ran into a bunch of pain points throughout the learning process here that I can share, but don't have a reason to believe you'll run into them too, so I don't want to overwhelm. More than happy to get into the specifics of any part of the process if you want!
> Copilot is implemented as a Embedded Domain Specific Language in Haskell. Currently Copilot 3.1 requires a version of the Glasgow Haskell Compiler (GHC) of at least 8.0 to be installed.
So yeah, looks like Haskell because it is Haskell! :)
If your app uses the JavaScriptCore[3] framework to run JS in a VM in-process directly, you have even more options for interfacing between JS and native code.
Note that this has to be explicitly hooked up by the app (i.e., none of this applies within, say, Safari).
[1]: https://developer.apple.com/documentation/webkit/wkuserconte...
[2]: https://developer.apple.com/documentation/webkit/wkuserconte...
[3]: https://developer.apple.com/documentation/javascriptcore