Compare commits
8 Commits
b921ddfc8d
...
localizati
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b5748cc5a | |||
| cd574b43fd | |||
| 8466a5601e | |||
| 845d1ae7d8 | |||
| 988bf72786 | |||
| 8557dc4399 | |||
| 7d1fcdb0c5 | |||
| bda78fed78 |
@@ -1,5 +1,4 @@
|
||||
baseURL = "https://danilafe.com"
|
||||
languageCode = "en-us"
|
||||
languageCode = "en"
|
||||
title = "Daniel's Blog"
|
||||
theme = "vanilla"
|
||||
pygmentsCodeFences = true
|
||||
@@ -11,3 +10,9 @@ summaryLength = 20
|
||||
endLevel = 4
|
||||
ordered = false
|
||||
startLevel = 3
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
baseURL = "https://danilafe.com"
|
||||
[languages.ru]
|
||||
baseURL = "https://ru.danilafe.com"
|
||||
|
||||
8
content/_index.ru.md
Normal file
8
content/_index.ru.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Daniel's Blog
|
||||
description: Персональный блог Данилы Федорина о функциональном программировании, дизайне компиляторов, и многом другом!
|
||||
---
|
||||
## Привет!
|
||||
Добро пожаловать на мой сайт. Здесь, я пишу на многие темы, включая фунциональное программирование, дизайн компилляторов, теорию языков программирования, и иногда компьютерные игры. Я надеюсь, что здесь вы найдете что-нибуть интересное!
|
||||
|
||||
Вы читаете русскою версию моего сайта. Я только недавно занялся его переводом, и до этого времени редко писал на русском. Я заранеее извиняюсь за присутствие орфографических или грамматических ошибок.
|
||||
97
content/blog/00_compiler_intro.ru.md
Normal file
97
content/blog/00_compiler_intro.ru.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: Пишем Компилятор Для Функционального Языка на С++, Часть 0 - Вступление
|
||||
date: 2019-08-03T01:02:30-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
description: "todo"
|
||||
---
|
||||
Год назад, я был записан на курс по компиляторам. Я ждал этого момента почти два учебных года: еще со времени школы меня интересовало создание языков программирования. Однако я был разочарован - заданный нам финальный проект полностью состоял из склеивания вместе написанных профессором кусочков кода. Склеив себе такой грустный компилятор, я не почувствовал бы никакой гордости. А я хотел бы гордиться всеми своими проектами.
|
||||
|
||||
Вместо стандартного задания, я решил -- с разрешением профессора -- написать компилятор для ленивого функционального языка, используя отличную книгу Саймона Пейтона Джоунса, _Implementing functional languages: a tutorial_. На курсе мы пользовались С++, и мой проект не был исключением. Получился прикольный маленький язык, и теперь я хочу рассказать вам, как вы тоже можете создать ваш собственный функциональный язык.
|
||||
|
||||
### Примечание к Русской Версии
|
||||
Вы читаете русскою версию этой статьи. Оригинал ее был написан год назад, и с тех пор объем всей серии немного изменился. Я планировал описать только те части компилятора, которые я успел закончить и сдать профессору: лексический анализ, синтаксический разбор, мономорфную проверку типов, и компиляцию простых выражений с помощью LLVM. Закончив и описав все эти части, я решил продолжать разрабатывать компилятор, и описал сборку мусора, полиморфную проверку типов, полиморфные структуры данных, а также компиляцию более сложных выражений. Вместо того чтобы писать наивный перевод английской версии -- притворяясь что я не знаю о перемене моих планов -- я буду вносить в эту версию изменения соответствующие сегодняшнему состоянию компилятора. Части статей не затронутые этими изменениями я тоже не буду переводить слово в слово, иначе они будут звучать ненатурально. Тем не менее техническое содержание каждой статьи будет аналогично содержанию ее английской версии, и код будет тот же самый.
|
||||
|
||||
### Мотивация
|
||||
Начать эту серию меня подтолкнули две причины.
|
||||
|
||||
Во-первых, почти все учебники и вступления к созданию компиляторов, с которыми я сталкивался, были написаны об императивных языках, часто похожих на C, C++, Python, или JavaScript. Я считаю, что в компиляции функциональных языков -- особенно ленивых -- есть много чего интересного, и все это относительно редко упоминается.
|
||||
|
||||
Во-вторых, меня вдохновили книги, как Software Foundations. Все содержание Software Foundations, например, написано в форме комментариев языка Coq. Таким образом, можно не только читать саму книгу, но и сразу же запускать находящийся рядом с комментариями код. Когда описываемый код под рукой, легче экспериментировать и интереснее читать. Принимая это во внимание, я выкладываю вместе с каждой статьей соответствующую версию компилятора; в самой статье описывается код именно из этой версии. Все части написанной мною программы полностью доступны.
|
||||
|
||||
### Обзор
|
||||
Прежде чем начинать наш проект, давайте обсудим, чего мы будем добиваться, и какими способами.
|
||||
|
||||
#### “Классические” Стадии Компилятора
|
||||
Части большинства компиляторов достаточно независимы друг от друга (по крайней мере в теории). Мы можем разделить их на следующие шаги:
|
||||
|
||||
* Лексический анализ
|
||||
* Синтаксический разбор
|
||||
* Анализ и оптимизация
|
||||
* Генерация кода
|
||||
|
||||
Не все вышеописанные шаги встречаются в каждом компиляторе. Например, компилятор в моих статьях совсем не оптимизирует код. Также, в некоторых компиляторах присутствуют шаги не упомянутые в этом списке. Язык Idris -- как и многие другие функциональные языки -- переводится сначала в упрощённый язык “TT”, и только после этого проходит через анализ. Иногда, с целью ускорить компиляцию, несколько шагов производятся одновременно. В целом, все эти стадии помогут нам сориентироваться, но никаким образом нас не ограничат.
|
||||
|
||||
#### Темы, Которые Мы Рассмотрим
|
||||
Мы начнем с нуля, и пошагово построим компилятор состоящий из следующих частей:
|
||||
|
||||
* Лексического анализа с помощью программы Flex.
|
||||
* Синтаксического разбора с помощью программы Bison.
|
||||
* Сначала мономорфной, а позже полиморфной проверки типов.
|
||||
* Вычисления программ используя абстрактную машину G-machine.
|
||||
* Компиляции абстрактных инструкций G-machine используя LLVM.
|
||||
* Простого сбора мусора.
|
||||
|
||||
Наша цель - создать ленивый, функциональный язык.
|
||||
|
||||
#### Темы, Которые Мы Не Рассмотрим
|
||||
Для того, чтобы создать любую нетривиальную программу, нужно иметь значительный объем опыта и знаний; одному человеку было бы сложно научить всему этому. У меня буквально не хватило бы на это времени, да и исход такой попытки был бы неблагоприятным: опытным читателям было бы труднее извлечь из статей новую информацию, а неопытным читателям все равно было бы недостаточно подробно. Вместо того, чтобы портить таким образом свои статьи, я буду полагаться на то, что вы достаточно комфортно себя чувствуете с некоторыми темами. В число этих тем входят:
|
||||
|
||||
* [Теория алгоритмов](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2),
|
||||
более конкретно [теория автоматов](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%BE%D0%B2).
|
||||
Детерминированные и недетерминированные автоматы кратко упоминаются в первой статье во время лексического анализа, a синтаксический разбор мы выполним используя контекстно-свободную грамматику.
|
||||
* [Функциональное программирование](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), с легкой примесью [лямбда-исчисления](https://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%B8%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5).
|
||||
Мы будем пользоваться лямбда-функциями, каррированием, и системой типов Хиндли-Мильнер, которая часто встречается в языках семейства ML.
|
||||
* С++. Я стараюсь писать код правильно и по последним стандартам, но я не эксперт. Я не буду объяснять синтаксис или правила С++, но разумеется буду описывать что именно делает мой код с точки зрения компиляторов.
|
||||
|
||||
#### Синтаксис Нашего Языка
|
||||
Саймон Пейтон Джоунс, в одном из своих [~~двух~~ многочисленных](https://www.reddit.com/r/ProgrammingLanguages/comments/dsu115/compiling_a_functional_language_using_c/f6t52mh?utm_source=share&utm_medium=web2x&context=3) трудов на тему функциональных языков, отметил что большинство из этих языков по сути очень похожи друг на друга; часто, главная разница состоит именно в их синтаксисе. На данный момент, выбор синтаксиса - наша главная степень свободы. Нам точно нужно предоставить доступ к следующим вещам:
|
||||
|
||||
* Декларациям функций
|
||||
* Вызову функций
|
||||
* Арифметике
|
||||
* Aлгебраическим типам данных
|
||||
* Сопоставлению с образцом
|
||||
|
||||
Позже, мы добавим к этому списку выражения let/in и лямбда-функции. С арифметикой разобраться не сложно - числа будут писаться просто как `3`, значения выражений как `1+2*3` будут высчитываться по обычным математическим правилам. Вызов функций ненамного сложнее. Выражение `f x` будет значить “вызов функции `f` с параметром `x`”, а `f x + g y` - “сумма значений `f x` и `g y`”. Заметьте, что вызов функций имеет приоритет выше приоритета арифметических операций.
|
||||
|
||||
Теперь давайте придумаем синтаксис для деклараций функций. Я предлогаю следующий вариант:
|
||||
|
||||
```
|
||||
defn f x = { x + x }
|
||||
```
|
||||
|
||||
А для типов данных:
|
||||
|
||||
```
|
||||
data List = { Nil, Cons Int List }
|
||||
```
|
||||
|
||||
Заметьте, что мы пока пользуемся мономорфными декларациями типов данных. Позже, в одиннадцатой части, мы добавим синтаксис для полиморфных деклараций.
|
||||
|
||||
В последнюю очередь, давайте определимся с синтаксисом сопоставления с образцом:
|
||||
|
||||
```
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> { x }
|
||||
}
|
||||
```
|
||||
|
||||
Представленная выше распечатка читается как: “если лист `l` сопоставим с `Nil`, то все выражение возвращает значение `0`; иначе, если лист сопоставим с `Cons x xs` (что, опираясь на декларацию `List`, означает, что лист состоит из значений `x`, с типом `Int`, и `xs`, с типом `List`), то выражение возвращает `x`”.
|
||||
|
||||
Вот и конец нашего обзора! В следующей статье, мы начнем с лексического анализа, что является первым шагом в процессе трансформации программного текста в исполняемые файлы.
|
||||
|
||||
### Список Статей
|
||||
* Ой! Тут как-то пусто.
|
||||
* Вы, наверно, читаете черновик.
|
||||
* Если нет, то пожалуйста напишите мне об этом!
|
||||
@@ -1,311 +0,0 @@
|
||||
---
|
||||
title: "How Many Values Does a Boolean Have?"
|
||||
date: 2020-08-21T23:05:55-07:00
|
||||
tags: ["Java", "Haskell", "C and C++"]
|
||||
---
|
||||
|
||||
A friend of mine recently had an interview for a software
|
||||
engineering position. They later recounted to me the content
|
||||
of the technical questions that they had been asked. Some had
|
||||
been pretty standard:
|
||||
|
||||
* __"What's the difference between concurrency
|
||||
and parallelism?"__ -- a reasonable question given that Go was
|
||||
the company's language of choice.
|
||||
* __"What's the difference between a method and a function?"__ --
|
||||
a little more strange, in my opinion, since the difference
|
||||
is of little _practical_ use.
|
||||
|
||||
But then, they recounted a rather interesting question:
|
||||
|
||||
> How many values does a bool have?
|
||||
|
||||
Innocuous at first, isn't it? Probably a bit simpler, in fact,
|
||||
than the questions about methods and functions, concurrency
|
||||
and parallelism. It's plausible that a candidate
|
||||
has not done much concurrent or parallel programming in their
|
||||
life, or that they came from a language in which functions
|
||||
were rare and methods were ubiquitous. It's not plausible,
|
||||
on the other hand, that a candidate applying to a software
|
||||
engineering position has not encountered booleans.
|
||||
|
||||
If you're genuinely unsure about the answer to the question,
|
||||
I think there's no reason for me to mess with you. The
|
||||
simple answer to the question -- as far as I know -- is that a boolean
|
||||
has two values. They are `true` and `false` in Java, or `True` and `False`
|
||||
in Haskell, and `1` and `0` in C. A boolean value is either true or false.
|
||||
|
||||
So, what's there to think about? There are a few things, _ackshually_.
|
||||
Let's explore them, starting from the theoretical perspective.
|
||||
|
||||
### Types, Values, and Expressions
|
||||
Boolean, or `bool`, is a type. Broadly speaking, a type
|
||||
is a property of _something_ that defines what the _something_
|
||||
means and what you can do with it. That _something_ can be
|
||||
several things; for our purposes, it can either be an
|
||||
_expression_ in a programming language (like those in the form `fact(n)`)
|
||||
or a value in that same programming language (like `5`).
|
||||
|
||||
Dealing with values is rather simple. Most languages have finite numbers,
|
||||
usually with \\(2^{32}\\) values, which have type `int`,
|
||||
`i32`, or something in a similar vein. Most languages also have
|
||||
strings, of which there are as many as you have memory to contain,
|
||||
and which have the type `string`, `String`, or occasionally
|
||||
the more confusing `char*`. Most languages also have booleans,
|
||||
as we discussed above.
|
||||
|
||||
The deal with expressions is a more interesting. Presumably
|
||||
expressions evaluate to values, and the type of an expression
|
||||
is then the type of values it can yield. Consider the following
|
||||
snippet in C++:
|
||||
|
||||
```C
|
||||
int square(int x) {
|
||||
return x * x;
|
||||
}
|
||||
```
|
||||
|
||||
Here, the expression `x` is known to have type `int` from
|
||||
the type signature provided by the user. Multiplication
|
||||
of integers yields an integer, and so the type of `x*x` is also
|
||||
of type `int`. Since `square(x)` returns `x*x`, it is also
|
||||
of type `int`. So far, so good.
|
||||
|
||||
Okay, how about this:
|
||||
|
||||
```C++
|
||||
int meaningOfLife() {
|
||||
return meaningOfLife();
|
||||
}
|
||||
```
|
||||
|
||||
No, wait, doesn't say "stack overflow" just yet. That's no fun.
|
||||
And anyway, this is technically a tail call, so maybe our
|
||||
C++ compiler can avoid growing the stack. And indeed,
|
||||
flicking on the `-O2` flag in this [compiler explorer example](https://godbolt.org/z/9cv4nY),
|
||||
we can see that no stack growth is necessary: it's just
|
||||
an infinite loop. But `meaningOfLife` will never return a value. One could say
|
||||
this computation _diverges_.
|
||||
|
||||
Well, if it diverges, just throw the expression out of the window! That's
|
||||
no `int`! We only want _real_ `int`s!
|
||||
|
||||
And here, we can do that. But what about the following:
|
||||
|
||||
```C++
|
||||
inf_int collatz(inf_int x) {
|
||||
if(x == 1) return 1;
|
||||
if(x % 2 == 0) return collatz(x/2);
|
||||
return collatz(x * 3 + 1);
|
||||
}
|
||||
```
|
||||
|
||||
Notice that I've used the fictitious type
|
||||
`inf_int` to represent integers that can hold
|
||||
arbitrarily large integer values, not just the 32-bit ones.
|
||||
That is important for this example, and I'll explain why shortly.
|
||||
|
||||
The code in the example is a simulation of the process described
|
||||
in the [Collatz conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture).
|
||||
Given an input number `x`, if the number is even, it's divided in half,
|
||||
and the process continues with the halved number. If, on the other
|
||||
hand, the number is odd, it's multiplied by 3, 1 is added to it,
|
||||
and the process continues with _that_ number. The only way for the
|
||||
process to terminate is for the computation to reach the value 1.
|
||||
|
||||
Why does this matter? Because as of right now, __nobody knows__
|
||||
whether or not the process terminates for all possible input numbers.
|
||||
We have a strong hunch that it does; we've checked a __lot__
|
||||
of numbers and found that the process terminates for them.
|
||||
This is why 32-bit integers are not truly sufficient for this example;
|
||||
we know empirically that the function will terminate for them.
|
||||
|
||||
But why does _this_ matter? Well, it matters because we don't know
|
||||
whether or not this function will diverge, and thus, we can't
|
||||
'throw it out of the window' like we wanted to with `meaningOfLife`!
|
||||
In general, it's _impossible to tell_ whether or not a program will
|
||||
terminate; that is the [halting problem](https://en.wikipedia.org/wiki/Halting_problem).
|
||||
So, what do we do?
|
||||
|
||||
It turns out to be convenient -- formally -- to treat the result of a diverging computation
|
||||
as its own value. This value is usually called 'bottom', and written as \\(\\bot\\).
|
||||
Since in most programming languages, you can write a nonterminating expression or
|
||||
function of any type, this 'bottom' is included in _all_ types. So in fact, the
|
||||
possible values of `unsigned int` are \\(\\bot, 0, 1, 2, ...\\) and so on.
|
||||
As you may have by now guessed, the same is true for a boolean: we have \\(\\bot\\), `true`, and `false`.
|
||||
|
||||
### Haskell and Bottom
|
||||
You may be thinking:
|
||||
|
||||
> Now he's done it; he's gone off the deep end with all that programming language
|
||||
theory. Tell me, Daniel, where the heck have you ever encountered \\(\\bot\\) in
|
||||
code? This question was for a software engineering interview, after all!
|
||||
|
||||
You're right; I haven't _specifically_ seen the symbol \\(\\bot\\) in my time
|
||||
programming. But I have frequently used an equivalent notation for the same idea:
|
||||
`undefined`. In fact, here's a possible definition of `undefined` in Haskell:
|
||||
|
||||
```
|
||||
undefined = undefined
|
||||
```
|
||||
|
||||
Just like `meaningOfLife`, this is a divergent computation! What's more is that
|
||||
the type of this computation is, in Haskell, `a`. More explicitly -- and retreating
|
||||
to more mathematical notation -- we can write this type as: \\(\\forall \\alpha . \\alpha\\).
|
||||
That is, for any type \\(\\alpha\\), `undefined` has that type! This means
|
||||
`undefined` can take on _any_ type, and so, we can write:
|
||||
|
||||
```Haskell
|
||||
myTrue :: Bool
|
||||
myTrue = True
|
||||
|
||||
myFalse :: Bool
|
||||
myFalse = False
|
||||
|
||||
myBool :: Bool
|
||||
myBool = undefined
|
||||
```
|
||||
|
||||
In Haskell, this is quite useful. For instance, if one's in the middle
|
||||
of writing a complicated function, and wants to check their work so far,
|
||||
they can put 'undefined' for the part of the function they haven't written.
|
||||
They can then compile their program; the typechecker will find any mistakes
|
||||
they've made so far, but, since the type of `undefined` can be _anything_,
|
||||
that part of the program will be accepted without second thought.
|
||||
|
||||
The language Idris extends this practice with the idea of typed holes,
|
||||
where you can leave fragments of your program unwritten, and ask the
|
||||
compiler what kind of _thing_ you need to write to fill that hole.
|
||||
|
||||
### Java and `null`
|
||||
Now you may be thinking:
|
||||
|
||||
> This whole deal with Haskell's `undefined` is beside the point; it doesn't
|
||||
really count as a value, since it's just a nonterminating
|
||||
expression. What you're doing is a kind of academic autofellatio.
|
||||
|
||||
Alright, I can accept this criticism. Perhaps just calling a nonterminating
|
||||
function a value _is_ far-fetched (even though in [denotational semantics](https://en.wikipedia.org/wiki/Denotational_semantics)
|
||||
we _do_ extend types with \\(\\bot\\)). But denotational semantics are not
|
||||
the only place where types are implicitly extend with an extra value;
|
||||
let's look at Java.
|
||||
|
||||
In Java, we have `null`. At the
|
||||
core language level, any function or method that accepts a class can also take `null`;
|
||||
if `null` is not to that function or method's liking, it has to
|
||||
explicitly check for it using `if(x == null)`.
|
||||
|
||||
This `null` value does not at first interact with booleans.
|
||||
After all, Java's booleans are not classes. Unlike classes, which you have
|
||||
to allocate using `new`, you can just throw around `true` and `false` as you see
|
||||
fit. Also unlike classes, you simply can't assign `null` to a boolean value.
|
||||
|
||||
The trouble is, the parts of Java dealing with _generics_, which allow you to write
|
||||
polymorphic functions, can't handle 'primitives' like `bool`. If you want to have an `ArrayList`
|
||||
of something, that something _must_ be a class.
|
||||
But what if you really _do_ want an `ArrayList` of booleans? Java solves this problem by introducing
|
||||
'boxed' booleans: they're primitives wrapped in a class, called `Boolean`. This class
|
||||
can then be used for generics.
|
||||
|
||||
But see, this is where `null` has snuck in again. By allowing `Boolean` to be a class
|
||||
(thereby granting it access to generics), we've also given it the ability to be null.
|
||||
This example is made especially compelling because Java supports something
|
||||
they call [autoboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html):
|
||||
you can directly assign a primitive to a variable of the corresponding boxed type.
|
||||
Consider the example:
|
||||
|
||||
```Java
|
||||
Boolean myTrue = true;
|
||||
Boolean myFalse = false;
|
||||
Boolean myBool = null;
|
||||
```
|
||||
|
||||
Beautiful, isn't it? Better yet, unlike Haskell, where you can't _really_
|
||||
check if your `Bool` is `undefined` (because you can't tell whether
|
||||
a non-terminating computation is as such), you can very easily
|
||||
check if your `Boolean` is `true`, `false`, or `null`:
|
||||
|
||||
```Java
|
||||
assert myTrue != myFalse;
|
||||
assert myFalse != myBool;
|
||||
assert myTrue != myBool;
|
||||
```
|
||||
|
||||
We're okay to use `!=` here, instead of `equals`, because it so happens
|
||||
each boxed instance of a `boolean` value
|
||||
[refers to the same `Boolean` object](https://stackoverflow.com/questions/28636738/equality-of-boxed-boolean).
|
||||
In fact, this means that a `Boolean` variable can have __exactly__ 3 values!
|
||||
|
||||
### C and Integers
|
||||
Oh the luxury of having a type representing booleans in your language!
|
||||
It's almost overly indulgent compared to the spartan minimalism of C.
|
||||
In C, boolean conditions are represented as numbers. You can perhaps get
|
||||
away with throwing around `char` or `short int`, but even then,
|
||||
these types allow far more values than two!
|
||||
|
||||
```C
|
||||
unsigned char test = 255;
|
||||
while(test) test -= 1;
|
||||
```
|
||||
|
||||
This loop will run 255 times, thereby demonstrating
|
||||
that C has at least 255 values that can be used
|
||||
to represent the boolean `true`.
|
||||
|
||||
There are other languages
|
||||
with this notion of 'truthy' and 'falsey' values, in which
|
||||
something not exactly `true` or `false` can be used as a condition. However,
|
||||
some of them differ from C in that they also extend this idea
|
||||
to equality. In JavaScript:
|
||||
|
||||
```JavaScript
|
||||
console.assert(true == 1)
|
||||
console.assert(false == 0)
|
||||
```
|
||||
|
||||
Then, there are still exactly two distinct boolean values
|
||||
modulo `==`. No such luck in C, though! We have 256 values that fit in `unsigned char`,
|
||||
all of which are also distinct modulo `==`. Our boolean
|
||||
variable can contain all of these values. And there is no
|
||||
respite to be found with `enum`s, either. We could try define:
|
||||
|
||||
```C
|
||||
enum bool { TRUE, FALSE };
|
||||
```
|
||||
|
||||
Unfortunately, all this does is define `bool` to be a numeric
|
||||
type that can hold at least 2 distinct values, and define
|
||||
numeric constants `TRUE` and `FALSE`. So in fact, you can
|
||||
_still_ write the following code:
|
||||
|
||||
```C
|
||||
enum bool b1 = TRUE;
|
||||
enum bool b2 = FALSE;
|
||||
enum bool b3 = 15;
|
||||
```
|
||||
|
||||
And so, no matter how hard you try, your 'boolean'
|
||||
variable can have many, many values!
|
||||
|
||||
### Conclusion
|
||||
I think that 'how many values does a boolean have' is a strange
|
||||
question. Its purpose can be one of two things:
|
||||
|
||||
* The interviewer expected a long-form response such as this one.
|
||||
This is a weird expectation for a software engineering candidate -
|
||||
how does knowing about \\(\\bot\\), `undefined`, or `null` help in
|
||||
creating software, especially if this information is irrelevant
|
||||
to the company's language of choice?
|
||||
* The interviewer expected the simple answer. In that case,
|
||||
my previous observation applies: what software engineering
|
||||
candidate has _not_ seen a boolean in their time programming?
|
||||
Surely candidates are better screened before they are offered
|
||||
an interview?
|
||||
|
||||
Despite the question's weirdness, I think that the resulting
|
||||
investigation of the matter -- outside of the interview setting --
|
||||
is useful, and perhaps, in a way, enlightening. It may help
|
||||
one understand the design choices made in _their_ language of choice,
|
||||
and how those choices shape the code that they write.
|
||||
|
||||
That's all I have! I hope that you found it interesting.
|
||||
@@ -103,17 +103,6 @@ needed to compute the final answer can exist, unsimplified, in the tree.
|
||||
Why don't we draw a few graphs to get familiar with the idea?
|
||||
|
||||
### Visualizing Graphs and Their Reduction
|
||||
__A word of caution__: the steps presented below may significantly differ
|
||||
from the actual graph reduction algorithms used by modern compilers.
|
||||
In particular, this section draws a lot of ideas from Simon Peyton Jones' book,
|
||||
[_Implementing functional languages: a tutorial_](https://www.microsoft.com/en-us/research/publication/implementing-functional-languages-a-tutorial/).
|
||||
However, modern functional compilers (i.e. GHC) use a much more
|
||||
complicated abstract machine for evaluating graph-based code,
|
||||
based on -- from what I know -- the [spineless tagless G-machine](https://www.microsoft.com/en-us/research/wp-content/uploads/1992/04/spineless-tagless-gmachine.pdf).
|
||||
In short, this section, in order to build intuition, walks through how a functional program
|
||||
evaluated using graph reduction _may_ behave; the actual details
|
||||
depend on the compiler.
|
||||
|
||||
Let's start with something that doesn't have anything fancy. We can
|
||||
take a look at the graph of the expression:
|
||||
|
||||
|
||||
Binary file not shown.
@@ -28,7 +28,6 @@ pre code {
|
||||
border: $code-border;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
|
||||
@@ -9,7 +9,7 @@ $toc-border-color: $code-border-color;
|
||||
@include margin-content-left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
align-items: end;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
em {
|
||||
|
||||
35
themes/vanilla/i18n/en.toml
Normal file
35
themes/vanilla/i18n/en.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[Home]
|
||||
other = "Home"
|
||||
|
||||
[About]
|
||||
other = "About"
|
||||
|
||||
[Resume]
|
||||
other = "Resume"
|
||||
|
||||
[Tags]
|
||||
other = "Tags"
|
||||
|
||||
[RecentPosts]
|
||||
other = "Recent posts"
|
||||
|
||||
[AllPosts]
|
||||
other = "All Posts"
|
||||
|
||||
[PostedOn]
|
||||
other = "Posted on {{ .Date.Format \"January 2, 2006\" }}."
|
||||
|
||||
[TableOfContents]
|
||||
other = "Table of Contents"
|
||||
|
||||
[ReadingTime]
|
||||
other = "{{ .WordCount }} words, about {{ .ReadingTime }} minutes to read."
|
||||
|
||||
[Note]
|
||||
other = "note"
|
||||
|
||||
[Tagged]
|
||||
other = "Tagged \"{{ .Title }}\""
|
||||
|
||||
[AllTags]
|
||||
other = "Below is a list of all the tags ever used on this site."
|
||||
35
themes/vanilla/i18n/ru.toml
Normal file
35
themes/vanilla/i18n/ru.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[Home]
|
||||
other = "Главная"
|
||||
|
||||
[About]
|
||||
other = "О Сайте"
|
||||
|
||||
[Resume]
|
||||
other = "Резюме"
|
||||
|
||||
[Tags]
|
||||
other = "Метки"
|
||||
|
||||
[RecentPosts]
|
||||
other = "Недавние статьи"
|
||||
|
||||
[AllPosts]
|
||||
other = "Все Статьи"
|
||||
|
||||
[PostedOn]
|
||||
other = "Статья опубликована {{ .Date.Format \"January 2, 2006\" }}."
|
||||
|
||||
[TableOfContents]
|
||||
other = "Оглавление"
|
||||
|
||||
[ReadingTime]
|
||||
other = "{{ .WordCount }} слов, чтение займет примерно {{ .ReadingTime }} минут."
|
||||
|
||||
[Note]
|
||||
other = "примечание"
|
||||
|
||||
[Tagged]
|
||||
other = "Статьи c меткой \"{{ .Title }}\""
|
||||
|
||||
[AllTags]
|
||||
other = "Ниже приводится список всех меток на сайте."
|
||||
@@ -6,14 +6,14 @@
|
||||
<a class="button" href="{{ $.Site.BaseURL }}/tags/{{ . | urlize }}">{{ . }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
<p>Posted on {{ .Date.Format "January 2, 2006" }}.</p>
|
||||
<p>{{ i18n "PostedOn" . }}</p>
|
||||
</div>
|
||||
|
||||
<div class="post-content">
|
||||
{{ if not (eq .TableOfContents "<nav id=\"TableOfContents\"></nav>") }}
|
||||
<div class="table-of-contents">
|
||||
<div class="wrapper">
|
||||
<em>Table of Contents</em>
|
||||
<em>{{ i18n "TableOfContents" }}</em>
|
||||
{{ .TableOfContents }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{ define "main" }}
|
||||
{{ .Content }}
|
||||
|
||||
Recent posts:
|
||||
{{ i18n "RecentPosts" }}:
|
||||
<ul class="post-list">
|
||||
{{ range first 10 (where (where .Site.Pages.ByDate.Reverse "Section" "blog") ".Kind" "!=" "section") }}
|
||||
{{ partial "post.html" . }}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
</div>
|
||||
<nav>
|
||||
<div class="container">
|
||||
<a href="/">Home</a>
|
||||
<a href="/about">About</a>
|
||||
<a href="/">{{ i18n "Home" }}</a>
|
||||
<a href="/about">{{ i18n "About" }}</a>
|
||||
<a href="https://github.com/DanilaFe">GitHub</a>
|
||||
<a href="/Resume-Danila-Fedorin.pdf">Resume</a>
|
||||
<a href="/tags">Tags</a>
|
||||
<a href="/blog">All Posts</a>
|
||||
<a href="/Resume-Danila-Fedorin.pdf">{{ i18n "Resume" }}</a>
|
||||
<a href="/tags">{{ i18n "Tags" }}</a>
|
||||
<a href="/blog">{{ i18n "AllPosts" }}</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<li>
|
||||
<a href="{{ .Permalink }}" class="post-title">{{ .Title }}</a>
|
||||
<p class="post-wordcount">{{ .WordCount }} words, about {{ .ReadingTime }} minutes to read.</p>
|
||||
<p class="post-wordcount">{{ i18n "ReadingTime" . }}</p>
|
||||
<p class="post-preview">{{ .Summary }} . . .</p>
|
||||
</li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<label class="sidenote-label" for="numbernote-{{ $id }}">({{ $id }})</label>
|
||||
<input class="sidenote-checkbox" type="checkbox" id="numbernote-{{ $id }}"></input>
|
||||
<span class="sidenote-content sidenote-{{ .Get 0 }}">
|
||||
<span class="sidenote-delimiter">[note:</span>
|
||||
<span class="sidenote-delimiter">[{{ i18n "note" }}:</span>
|
||||
{{ .Inner }}
|
||||
<span class="sidenote-delimiter">]</span>
|
||||
</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<label class="sidenote-label" for="{{ .Get 1 }}">{{ .Get 2 }}</label>
|
||||
<input class="sidenote-checkbox" type="checkbox" id="{{ .Get 1 }}"></input>
|
||||
<span class="sidenote-content sidenote-{{ .Get 0 }}">
|
||||
<span class="sidenote-delimiter">[note:</span>
|
||||
<span class="sidenote-delimiter">[{{ i18n "Note" }}:</span>
|
||||
{{ .Inner }}
|
||||
<span class="sidenote-delimiter">]</span>
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{ define "main" }}
|
||||
<h2>Tagged "{{ .Title }}"</h2>
|
||||
<h2>{{ i18n "Tagged" . }}</h2>
|
||||
|
||||
<ul class="post-list">
|
||||
{{ range .Pages.ByDate.Reverse }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
Below is a list of all the tags ever used on this site.
|
||||
<h2>{{ i18n "Tags" }}</h2>
|
||||
{{ i18n "AllTags" }}
|
||||
|
||||
<ul>
|
||||
{{ range sort .Pages "Title" }}
|
||||
|
||||
Reference in New Issue
Block a user