housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
Tumblr remains alive, and I get more engagement over there and there's more engaging content for me to read. So while this is a nicer website, I am still inexorably drawn towards using Tumblr. ¯\_(ツ)_/¯

(https://house-carpenter.tumblr.com/ in case anybody doesn't know the URL)
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)


I was wondering what the name was of the lullaby I used to listen to to go to sleep when I was little (I used to have a blue plastic rabbit with a string hanging off it, which I'd yank to make it play the tune.) Turns out it's a Brahms composition.

Now if only I could find the name of the melody the ice cream vans used to play...

(Actually, it might have just been Greensleeves. I feel like there might have been another melody other than Greensleeves I heard a lot, but I might be imagining it.)
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)


Judy Henske is my new favourite folk songstress. I like the humour in this recording. A lot of folk music is done in a deadly serious manner, but many folk songs are somewhat ridiculous and that side of it should be brought out more often.
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
Been trying to catch up on the Current Music lately; here are my opinions:

"Sweet but Psycho" (Ava Max): Decent generic pop

"We Built This City" (LadBaby): Part of an esteemed tradition of ridiculous British novelty singles, therefore one has to like it

"thank u, next" (Ariana Grande): Really nice stuff, quite interesting and weird; definitely a pleasant surprise compared to what I was expecting, not having listened to Ariana before

"Shallow" (Lady Gaga & Bradley Cooper): Excellent ballad, the only song of the year I'm happy to have on my Spotify (though TBH "thank u, next" is just as good, it's just not really consonant with what I normally listen to)

"Promises" (Calvin Harris & Sam Smith): Unremarkable generic pop

"Funky Friday" (Dave ft. Fredo): Unremarkable generic rap

"Eastside" (Benny Blanco, Halsey & Khalid): Very nice little song, both musically and thematically

"Shotgun" (George Ezra): Thoroughly decent

"In My Feelings" (Drake): Boring rap (sorry, I don't get Drake)

"Three Lions" (Baddiel, Skinner & Lightning Seeds): Good stuff, appropriate for purpose

"Solo" (Clean Bandit ft. Demi Lovato): Demi Lovato's singing voice is one of the most horrible things in the universe, and I think in this one she's singing about... masturbation? Ewww

"I'll Be There" (Jess Glynne): Nice stuff, she has a great voice; "Thursday" is better though

"One Kiss" (Calvin Harris & Dua Lipa): Decent generic pop, but I don't understand why this was the best-selling single of 2018

"Nice for What" (Drake): Regular Drake trash, at least it has samples

"Freaky Friday" (Lil Dicky ft. Chris Brown): Somewhat amusing content, but musically trash

"These Days" (Rudimental ft. Jess Glynne, Macklemore & Dan Caplen): Fairly good, at least when Jess Glynne is singing

"God's Plan" (Drake): This is OK I guess, it has a nice atmosphere, and Drake's monotonous drawl actually gels reasonably well with it

"River" (Eminem ft. Ed Sheeran): Generic angry Eminem song

"Perfect" (Ed Sheeran): Pretty good straightforward love song
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
Today in new life experiences: I was shat on by a pigeon.
housecarpenter: A drawing of a woodlouse. (woodlouse)
I made a proper blog post for the first time in over a year! Thus ends my longest-ever blogging hiatus (at least since 2014 when I properly started blogging; I did make a couple of posts back in 2011 but never got into it).

Here's the link:

Reversing a Linked List in Place

housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
I fixed a bug today which was mildly interesting. It was caused by a local variable in a function having the same name as an unrelated imported module which was referenced earlier in the function. So the code was of this shape:

import x

...

def f():
    x.g()

    ...

    x = a


When g was called, an error occurred with the message "local variable x referenced before assignment". Now when I was debugging this, all I saw at first was the x.g() line (since that's the line the error occurred at), and I scrolled up, trying to find where the x variable had been defined, eventually finding that x was the name of the module. This left me rather confused, since I couldn't imagine how the module name x was getting interpreted as the name of a local variable when it hadn't been reassigned at any point earlier in the code.

Of course, the answer is that the later reassignment was responsible. Python (like most programming languages, I believe) allocates local variables on the call stack, so it has to know what local variables there are as it calls the function, before it executes the function body. So it effectively does an initial pass over the body looking for assignments, and notices the x = a line and rebinds the name x to a local variable, before it executes the x.g() line.

Now I see why C89 requires you to put all variable declarations at the start of a function! OK, I don't know if this was ever the actual rationale behind that restriction, but it does seem like a good reason for it. The C89 restriction makes it clear that local variable names have to be valid across the whole body of a function, not just from the line on which they are declared onwards.
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)


One of my dad's co-workers gave me a bunch of bird field guides from the 1970s!

This is pretty much the perfect Christmas gift. And it's from somebody I don't even know :) (although I guess my dad has been telling him about my interests.)
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)


Heard this song on a Top of the Pops rerun the other day. I like it a lot.
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)


Today my mind was blown after I learned that terrestrial tadpoles exist.
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
Realized today that you can write the parse function from my earlier post in a much simpler way using iterators and recursion:

Expr = t.Union[str, t.Tuple['Expr']]

def parse(tokens: t.Iterable[str]) -> t.Iterator[Expr]:
    """Parse an iterable of Lisp tokens.

    Unmatched parentheses will be ignored.

    >>> tuple(parse(lex('a (b (c d))')))
    ('a', ('b', ('c', 'd')))
    >>> tuple(parse(lex('((a b) c (d e))')))
    ((('a', 'b'), 'c', ('d', 'e')),)
    >>> tuple(parse(lex('(a (b)')))
    (('a', ('b',)),)
    >>> tuple(parse(lex('a)(')))
    ('a',)
    """
    for token in tokens:
        if token == ')':
            return
        elif token == '(':
            yield tuple(parse(tokens))
        else:
            yield token
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
Some day, I would like to install a new tool at work without literally every step of the process going wrong somehow.
housecarpenter: A drawing of Bob Dylan singing into a microphone with white make-up on his face. (Default)
I'm proud of this pair of little S-expression-parsing functions (nonrecursive!):

from typing import Iterable, Iterator, List, Union
import string

def lex(code: str) -> Iterator[str]:
    """Lexes a string of Lisp code, returning an iterator over its tokens.

    >>> list(lex(''))
    []
    >>> list(lex('()ab cde'))
    ['(', ')', 'ab', 'cde']
    >>> list(lex('ab ( (b ))'))
    ['ab', '(', '(', 'b', ')', ')']
    """
    token: List[str] = []

    for c in code:
        if c == '(':
            if token:
                yield ''.join(token)
                token.clear()
            yield c
        elif c == ')':
            if token:
                yield ''.join(token)
                token.clear()
            yield c
        elif c in string.whitespace:
            if token:
                yield ''.join(token)
                token.clear()
        else:
            token.append(c)

    if token:
        yield ''.join(token)

class UnmatchedParenthesis(Exception):
    pass

Expression = Union[str, List['Expression']]

def parse(tokens: Iterable[str]) -> List[Expression]:
    """Parses an iterable of Lisp tokens, returning an abstract syntax tree.

    >>> parse(lex(''))
    []
    >>> parse(lex('a ((b) (c d) (e)) f'))
    ['a', [['b'], ['c', 'd'], ['e']], 'f']
    >>> parse(lex('(a)'))
    [['a']]
    >>> parse(lex('('))
    Traceback (most recent call last):
        ...
    UnmatchedParenthesis: 1 unmatched opening parenthesis
    >>> parse(lex('(((a)'))
    Traceback (most recent call last):
        ...
    UnmatchedParenthesis: 2 unmatched opening parentheses
    >>> parse(lex(')'))
    Traceback (most recent call last):
        ...
    UnmatchedParenthesis: unmatched closing parenthesis
    """
    tree: List[Expression] = []
    parent_nodes: List[List[Expression]] = [tree]

    for token in tokens:
        if token == '(':
            subtree: List[Expression] = []
            parent_nodes[-1].append(subtree)
            parent_nodes.append(subtree)
        elif token == ')':
            if len(parent_nodes) <= 1:
                raise UnmatchedParenthesis('unmatched closing parenthesis')
            parent_nodes.pop()
        else:
            parent_nodes[-1].append(token)
 
    if len(parent_nodes) > 1:
        unmatched_count: int = len(parent_nodes) - 1
        ending: str = (
            'is' if unmatched_count == 1
            else 'es'
        )
        raise UnmatchedParenthesis(
            f'{unmatched_count} unmatched opening parenthes{ending}'
        )

    return tree

if __name__ == '__main__':
    import doctest
    doctest.testmod()
Page generated Apr. 23rd, 2025 12:57 pm
Powered by Dreamwidth Studios