Skip to content
Keyboard And Me

Weekly Reading 2021-06-06

Chinese Language

If you want to read classical Chinese text printed or hand copied centuries ago:

  • 中国古籍保护网 is a project of the National library that incorporates all the links to local libraries that have open-access scans of classical books. It’s not that open though. Some of them requires you to have an account.
  • 汉典重光 is a project by Alibaba that aims to digitize the rare classical copies. It mainly focuses on the books that are located abroad.

Spanish Language

Dr. Joaquim Llisterri’s homepage contains some linguistic resources about Spanish (as well as Catalan). There I found some useful terminologies that doesn’t exist in Wikipedia.

Silabeador by tulengua.es is a useful tool to separate Spanish words into syllables.

What’s great about the Spanish language is that the syllables are 100% computable. I have an NPM package that does it.

Programming

None of the Above

This Taobao Shop prints me a book of 450 pages for as little as 25 yuan.

Learning Rust 01

Recently I’m trying to learn rust by rewriting one of my typescript libraries into rust. The purpose of the articles is that I record myself sorting things out and trying to understand stuff. They are mostly memos or notes that I serve myself. Though i’d be glad if you find it interesting, and even happier if you could point out errors and directions. Cheers.

The project I’m rewriting is silabacion. It divides Spanish words into syllables, and tries to provide as many phonetic features as possible. There isn’t much about it, just lots of string manipulation and pattern matching. It should be a good exercise to rewrite it with rust, and the result could be useful for those 5 people who process Spanish words with rust, so why not?

Starting small. First I want to convert some easy utility functions. For example this one:

const ACCENTED_VOWELS = ['á', 'é', 'í', 'ó', 'ú'];
const NON_ACCENTED_VOWELS = ['a', 'e', 'i', 'o', 'u', 'ü'];
const ALL_VOWELS = [...ACCENTED_VOWELS, ...NON_ACCENTED_VOWELS];

// ...

export function isVowel(char: string) {
  return ALL_VOWELS.includes(char.toLowerCase());
}

Let’s (or let me, because this is just me trying to sort things out) dive in. Note that until now I just have read the first few chapters of the book and a few articles online. I don’t know a lot of things.

Creating global constants

The lesson: You can’t create new constants based on existing constants.

In the typescript code, I created ALL_VOWELS with two existing arrays. However, with rust, if I want to define a const with a similar way, I’m out of luck. According to the documentation, only constant expressions are allowed for const definition. Rust’s equivalent of spreading isn’t a constant expression. So no luck, we have to do it the hard way:

pub const ALL_VOWELS: [char; 11] = ['á', 'é', 'í', 'ó', 'ú', 'a', 'e', 'i', 'o', 'u', 'ü'];

Getting lowercase

The lesson: ch.to_lowercase returns an iterator of chars, not a simple char.

It is necessary because in some languages, one upper case letter may have a lower case counterpart that consists of multiple letters, or vice versa. In German for example, the lower case letter ß is usually turned to SS in upper case.

In typescript, you get the lower case character by simply calling a function. Because typescript doesn’t distinguish between char and string, it doesn’t hurt if it doesn’t return an iterator. But in rust, that wouldn’t do.

pub fn is_vowel0(ch: &char) -> bool {
    match &ch.to_lowercase().to_string().chars().next() {
        Some(x) => ALL_VOWELS.contains(x),
        None => false,
    }
}

Note how much work we have to do with this. We have to turn the to_lowercase() result (an iterator) to a string, then to an iterator of chars, then get its first item, then check if it matches the predefined list.

Dumber, but Better Implementations

The above approach is enough to make the rust implementation slower than its node.js counterpart (I could be wrong, I haven’t tested). So I did the dumb method by letting ALL_VOWELS contain both upper and lower case letters.

pub const ALL_VOWELS: [char; 22] = ['á', 'é', 'í', 'ó', 'ú', 'a', 'e', 'i', 'o', 'u', 'ü', 'Á', 'É', 'Í', 'Ó', 'Ú', 'A', 'E', 'I', 'O', 'U', 'Ü'];

pub fn is_vowel1(ch: &char) -> bool {
    ALL_VOWELS.contains(ch)
}

This version is about 4 times faster than the previous implementation in my naive performance test. I believe it would be even faster in optimized release build.

Another implementation I’ve seen is like this, and I’ve created a similar one to test:

pub fn is_vowel2(c: &char) -> bool {
    match c {
        'á' | 'é' | 'í' | 'ó' | 'ú' | 'a' | 'e' | 'i' | 'o' | 'u' | 'ü' | 'Á' | 'É' | 'Í' | 'Ó'
        | 'Ú' | 'A' | 'E' | 'I' | 'O' | 'U' | 'Ü' => true,
        _ => false,
    }
}

The result code appears to be 10 to 20 times faster than the previous implementation. I don’t know why.

Another implementation could be using equals test:

pub fn is_vowel3(c: &char) -> bool {
    let c = c.clone();
    c == 'á'
        || c == 'é'
        || c == 'í'
        || c == 'ó'
        || c == 'ú'
        || c == 'a'
        || c == 'e'
        || c == 'i'
        || c == 'o'
        || c == 'u'
        || c == 'ü'
        || c == 'Á'
        || c == 'É'
        || c == 'Í'
        || c == 'Ó'
        || c == 'Ú'
        || c == 'A'
        || c == 'E'
        || c == 'I'
        || c == 'O'
        || c == 'U'
        || c == 'Ü'
}

This is slower than the previous one, probably because I had to clone the character. Or something else. Anyway, it’s too ugly, I think I’ll forget about it.

For now I’ll stick with is_vowel1 or is_vowel2 because they appear to be fast enough and not too ugly.

TODOs

  • I still don’t know which is more idiomatic.
  • I still don’t fully understand the performance result.

Learning Rust 02

Today I found an implementation of is_vowel by BartMassey. It’s a much advanced implementation. I’ll try to analyze the code. Bear in mind that there is still much that I don’t know or understand.

External dependencies

The Cargo.toml indicates that two external dependencies are used:

What is a cell-like type? See std::cell. It’s “shareable mutable container”, and the rest is a blur. I think I’ll revisit it later.

unicode-normalization provides traits to chars so they can be normalized. I’ve begin to feel that traits are indeed a smart idea. Though still lots to learn!

TODO: Read docs about std::cell and once_shell.

static or const

Here is how the library initiate the vowels to be checked against:

static BASE_VOWELS: Lazy<HashSet<char>> = Lazy::new(|| "aeiouAEIOU".chars().collect());

Lazy is provided by once_cell that does roughly what lazy_static! macro does.

TODO: Read https://docs.rs/lazy_static/1.4.0/lazy_static/

I think I’m almost sure that a simple const would suffice. Though the author may have his reasons.

Traits

The library implemented a trait for char that checks if it is a vowel, then added Sealed to char to prevent downstream reimplementation.

Or we could just use a simple function, like I did previously. So is traits the way?

Rust’s official blog published an article about traits. It says that traits are good for extension methods:

Traits can be used to extend an existing type (defined elsewhere) with new methods, for convenience, similarly to C#‘s extension methods. This falls directly out of the scoping rules for traits: you just define the new methods in a trait, provide an implementation for the type in question, and voila, the method is available.

So I guess it means that traits is the way to go?

24 Days of Hanzi

Preface

24 Days of Hanzi for Christmas 2020, originally published in Twitter.

Day 1

#24DaysOfHanzi 咖

Have you noticed that 咖 is pronounced kā in 咖啡(kāfēi, coffee), and gā in 咖喱(gāli, curry)?

I didn’t notice until yesterday, though I already knew. Human brains are funny devices.

The word 咖喱 doesn’t sound much like the English “curry”, to the point that for long I’ve had a misconception that garlic means 咖喱. Now that I know 咖喱 is the phonetic translation of the Tamil word “kari”, I feel much better. Though I still don’t know why it has to be “gā”.

There’s another usage: a 大咖(dàkā) is the main cast of a movie or show. By extension it can also refer to any kind of person of importance.

Here 咖 is the phonetic translation of “casting”.

Personally I use 大咖 I mean “connoisseur”, but that could be just me.

Day 2

#24DaysOfHanzi 樟 zhāng

If you walk the streets in the winter of southern China and you see evergreen trees by the roadside with no particular features, chances are you are seeing the 香樟, the camphor tree.

If you want to confirm, the seeds and leaves have a distinct fragrance when crushed, so it’s hard to miss.

https://en.wikipedia.org/wiki/Cinnamomum_camphora

樟 is a pictophonetic character in which 木 is the radical which indicates that it’s related to trees, and 章(zhāng) indicates the pronunciation. Characters containing 章 including 彰(manifest), 獐(Chinese water deer), 瘴(miasma), etc. They all share the same pronunciation.

There is also 障(hindrance, barricade) that is the 4th tone.

In Japan the tree is also known as 楠. However, the name 楠 in China is a different tree species.

Day 3

#24DaysOfHanzi 家 jiā

家 is home and hearth. It’s where there’s a fireplace, or in the case of the Chinese, it’s where the pig has a roof. In ancient China pigs are so priced that humans have to live outside(not true Rolling on the floor laughing )

家 is also someone who dominates a skill, as in 钢琴家(gāngqínjiā, pianist).

Notice that “dominate” traces back to Latin domus(master’s house), dominus(the master), dominatus(verbify). Convergent evolution much? Except no slavery is involved in 家.

But these are not today’s topic. The topic is “why is there a place called 陆家嘴(lùjiāzuǐ) in Shanghai?

陆 is a common family name, so 陆家 is 陆’s family. It’s quite common that Chinese toponyms is named after the family who live there.

Day 4

Then why 嘴?

嘴 is mouth, but 嘴 can also be protrusion(think about mouths of every non-human animal). If you look at the map 陆家嘴 is where the Huangpu river takes its big turn, looks like the land is protruding westwards. Hence the name.

But as Ryan Hayashi said, we are not done yet!

陆家嘴 follows the toponym format “X家Y”, where X is the family name, y is the geography feature. The format is extremely common in China. Last night I sacrificed sleep and went down the rabbit hole. I browsed the Chinese map for hours and got the following list:

张家港
张家界
石家庄
陆家嘴
沙家浜
庄家厍
赵家村
邵家湾
方家山
绍家坡
孙家沟
虎家丘
乔家梁
肖家垛
程家疙瘩
杨家台
李家垣
沈家铺
阎家社
白家岭
计家塘
江家岔
杨家坪
赵家垱
温家荡
崔家堡
罗家峪
韩家营
阎家峁
韩家川
牛家咀
王家滩
陈家潭
禅家岩

I did my best to find place names with non-repeating Y part. There’s definitely a lot more. I could expand it to a complete series, but then you probably won’t have time for Christmas. Here you’re seeing mountains, hills, rocks, rivers, valleys lakes, beaches, and a lot more.

This morning I found that the national database contains literally 1 million toponyms with the character 家. It also tried its best to explain the name origins. It’s a good place to start your research. Wish I knew it earlier.

Day 5

#24DaysOfHanzi 圈 quān

圈 is circle o ring. When pronounced juàn it’s still a ring, a ring of fence to keep your farm animal from wandering away, a pen that holds animal(not a pen that’s held by animal).

圈 is also sphere. 生物圈(shēngwùquān) is biosphere. 大气圈(dàqìquān) is atmosphere, although here it’s more often called 大气层(céng) which is literally atmostrata. It could be translated literally as 大气球 but then it’s a big balloon.

Day 6

#24DaysOfHanzi 转

From the dictionary:

转(zhuàn): 旋转(xuánzhuǎn)

The other day the 1st grade teacher said that xuánzhuàn is wrong. Turns out zhuǎn is “change position”, while zhuàn is “circle, rotate”, i.e. change position in a specific way.

Since almost everyone gets it wrong, will the dictionary change to reflect it? Perhaps.

Anyway, as long as you’re not a school student or you’re aiming to become a news broadcaster, you don’t have to care.

Day 7

#24DaysOfHanzi 非(fēi)

非 is no. It’s usually stuck before another word to form a negative. 非常(~ cháng, very) is literally uncommon. 非法(~ fǎ, illegal) is literally what it is. 非洲 Earth globe europe-africa (~ zhōu, Africa), however, is not a continent(nah it’s just phonetic translation).

非机动车 is engineless vehicle, but electric bikes are also 非机动车 despite they do have engines.

非遗 is short for 非物质文化遗产 or intangible cultural heritage, but it looks like non-heritage.

非典 is short for 非典型肺炎 or atypical pneumonia, but isn’t it too short.

Last but not least, 非字型停车位, 非-shaped parking lot, just because.

Day 8

#24DaysOfHanzi 行(xíng)

The Chinese say 行万里路, 读万卷书 - walk 10000 li of road, read 10000 scrolls of book - same thing.

行 used to be just “crossroad”, then it became “road” in general, then it verbified to “travel, proceed, practise” and is used to mean “ok, acceptable”.

It’s a 多音字 or heteronym. The other pronunciation is [háng], meaning “row”, “line of work, occupation”, “business” etc.

This is perhaps to much. I’m not a dictionary after all. I’ll focus on one word: 一行.

一行(yīháng)

  • a row: 一行白鹭上青天, a row of white egrets ascend to the blue sky.
  • a job: 你这一行风险挺高, your line of job involves in a lot of occupational hazard.

一行(yīxíng)

  • a group of people making a formal visit. (I’ll spare you with examples because I hate to translate bureaucracy. You can search for examples on news sites)

一行(yīxíng) is also a buddhist monk and astronomer of the 唐 dynasty who made some important measurements and revised the calendar.

Day 9

#24DaysOfHanzi 平(píng)

平 is horizontal, flat, smooth, plain, ordinary, steady, safe, peaceful, and a lot of other things.

平 used to be a popular name candidate. Nowadays parents don’t want plain and ordinary, so you don’t see it very often among kids.

There’s a game called 天下太平(great peace beneath the sky) which is a glorified version of rock paper scissors. I used to play it when I was little. Ask your Chinese friend and see if she knows about it.

The hospital basement where dead bodies are temporarily stored is called 太平间(room of great peace). Isn’t it beautiful name.

Day 10

#24DaysOfHanzi 佃(diàn)

Structurally it looks like a man standing by his crop field, but it actually means tenant, i.e. peasant without land of his own.

Day 11

#24DaysOfHanzi 猊(ní)

Isn’t it strange that the little marsupial from the South America, the little monkey of the mountain, is called 南猊(nánní, lion of the south) in Chinese?

なに?

At first 猊 or 狻猊(suān ní) used to refer to a mythical feline that preys upon tigers and leopards. Then it’s just lion, then it fell out of favor. Now zoologists borrowed the character to mean something completely different.

Day 12

#24DaysOfHanzi 药(yào, medicine) is 藥 in traditional Chinese, which is formed by “happy(樂)” and “herb(艹)”.

Nah, it’s just another pictophonetic character where 樂 is the phonetic part.

Day 13

#24DaysOfHanzi 砉(xū): The sound of bone separating from other tissues when the animal is being butchered.

#ThereIsAHanziForThat

砉騞(xūhuà) is the sound of arrow slicing through the air.

Day 14

#24DaysOfHanzi

You might have underestimated how confusing these emoji characters are to a Chinese person. I’ll list all my first impressions of them so you’ll understand.

Day 15

#24DaysOfHanzi 妹(mèì) younger sister.

I used to have a neighbor, a little old lady, who’s name was 徐小妹. Imagine being called “little sister” by everyone in your whole life hahahahah.

Day 16

#24DaysOfHanzi Looks like Google Translate uses English as the bridging language, which sometimes results in jokes.

类 is kind(noun), not kind(adjective).

Day 17

#24DaysOfHanzi 米(mǐ)is millet, then rice, then grain seeds in general, but it’s also meter the length unit.

Therefore, the length of 米(millet) is about 1/1000米(meter), or what they call 1 millimeter(毫米), which, as you see, is actually milletmeter.

Day 18

An ancient scholar said that 华(of Chinese) referrers to the beautiful clothes they wear, which is still quite superfluous.

Most probably the sound just represents a group of people or a region where they live, but the meaning is long lost in history,

Day 19

#24DaysOfHanzi 上(shǎng) is the 3rd tone in Mandarin Chinese, together with 去(4th in Mandarin) and 入(doesn’t exist in Mandarin), they form the group of dark tones called 仄(zè).

Incidentally, 上, 去, and 入 are all slangs of “fuck”.

Day 20

#24DaysOfHanzi 洋(yáng) it’s ocean, when used as adjective, it’s of/from the west, because westerners arrived China by ocean.

I think there’s a turning point, I can’t pin it down, but it’s when 洋气(west-like) lost it’s pejorative sense and became synonym of “modern”.

Day 21

I sometimes do this with Chinese, too: 上上边,下下边,前前面,后后面. If that’s just me, people say 进里边 and 出外边 all the time.

Also Chinese tends to mix 1d with 2d: “边(side)” and “面(area)” are synonyms in above usage.

#24DaysOfHanzi

Day 22

#24DaysOfHanzi 赢(yíng) is to profit, and more frequently, to win. It applies if you win a sports match, or a gamble, but usually not the lottery.

Examples: 阿尔法狗要赢(AlphaGo is winning). 柯洁只赢过阿法狗一局 Ke Jie won only once against AlphaGo. 柯洁对阿法狗赢面(chance of winning)不大,最后阿法狗是赢家(winner)。

It could also mean “achieve, gain”, usually as part of the word 赢得:

赢得尊重 gain respect 赢得掌声 gain applause 赢得胜利 achieve victory 赢得同情 gain sympathy

The character is hard. With so many parts cramming together, you need a magnifying glass to see its parts on the screen. It’s 亡 and 口 at the top, 月, 贝, and 凡 at bottom. I remembered the word by reciting 亡口月贝凡 until I was sure I got it. Long live rote memory.

Folk etymology has it that the character is formed by the five keys to victory: 亡(death, loss) - vigilance, 口(mouth) - communication, 月(moon, month) - perseverance, 贝(clam shell, which was used as currency) - resourceful, and 凡(plain, ordinary) - peace of mind.

Real etymology is a blur. The character in seal script looks like 贝(clam shell) combined with some kind of animal. Some think it’s dragon, some think it’s just potter wasp(蜾蠃). Note this time it’s 亡口月虫凡, and the pronunciation is different.

Real etymology is a blur. The character in seal script looks like 贝(clam shell) combined with some kind of animal. Some think it’s a dragon, some think it’s a different magical being, some think it’s just a potter wasp which we call 蜾guo蠃luo. Note this time it’s 亡口月虫凡, and the pronunciation is different.

Day 23

#24DaysOfHanzi 祝 used to be “priest”. What a priest does is “pray” with words or “prayers”. So 祝福 is literally “pray(or prayer) for happiness”. Nowadays it has weighed down to mean simply “wish”.

The character is a man with a strange head kneeling before the altar.

Day 24

#24DaysOfHanzi 曲(qū) is bent or turn or curve, be it L- or U- or C-shaped. It’s also an adjective describing the same thing.

The most recent form really f*cked up the shape trying to make everything squarish.

Every Static Blog Engine 1 - Gatsby

Preparation

I installed NodeJS 14 LTS with package managers.On Windows I installed scoop. On Mac I installed brew.

Install gatsby-cli

Installing gatsby-cli appears to be smooth on both platforms. I saw no error on both Windows and Mac.

npm install -g gatsby-cli

npm install is quite slow in China. There are Chinese NPM mirrors. If you don’t want to use the mirrors, you can use VPN to speed things up.

Initiate the blog

This is where the issues begin.

gatsby new weiwei-gatsby.github.io https://github.com/gatsbyjs/gatsby-starter-blog

Windows

I got error like this:

> mozjpeg@7.0.0 postinstall D:\code\weiwei-gatsby.github.io\node_modules\mozjpeg
> node lib/install.js

  ‼ getaddrinfo ENOENT raw.githubusercontent.com
  ‼ mozjpeg pre-build test failed
  i compiling from source
  × Error: Command failed: C:\Windows\system32\cmd.exe /s /c "autoreconf -fiv"
'autoreconf' �����ڲ����ⲿ���Ҳ���ǿ����еij���
�����������

Cool. I don’t even know where to look at. But I guess it’s because I don’t have autoconf. I have absolutely no idea how to install it. Someone asked the same issue on stackoverflow and nobody gave an answer.

I give up.

Mac

Install failed for the same package, but the error message is different.

npm ERR! code 1
npm ERR! path /Users/wei/code/weiwei.gb/node_modules/mozjpeg
npm ERR! command failed
npm ERR! command sh -c node lib/install.js
npm ERR! ⚠ connect ECONNREFUSED 0.0.0.0:443
npm ERR!   ⚠ mozjpeg pre-build test failed
npm ERR!   ℹ compiling from source
npm ERR!   ✖ Error: Command failed: /bin/sh -c ./configure --enable-static --disable-shared --disable-dependency-tracking --with-jpeg8 libpng_LIBS='/usr/local/lib/libpng16.a -lz' --enable-static --prefix="/Users/wei/code/weiwei.gb/node_modules/mozjpeg/vendor" --bindir="/Users/wei/code/weiwei.gb/node_modules/mozjpeg/vendor" --libdir="/Users/wei/code/weiwei.gb/node_modules/mozjpeg/vendor"
npm ERR! configure: error: no nasm (Netwide Assembler) found

At least the message is readable. I ran brew install nasm and it finally passed for me.

Conclusion

The install experience isn’t quite enjoyable. Nothing worked in the first place. I want something that works both on Windows and Mac. I can’t get it work on Windows.