Індекс
Вісім років в підпіллі
Чорт забирай!
Дайте мені ящик вонючого рому і той корабель-розвалюху. Почеплю на мачту вітрило та попливу зустрічати шторм, лице в лице, лиш би ніколи більше не жити в підпіллі!
Примітка.
Підпілля (англ. underground) – це така область під підлогою, в яку ховаються ті переможені, котрі не вважають себе переможеними. Навіть більше, іноді вони вважають себе переможцями, і враховуючи малу полпуляцію підпільників, так і стається – під підлогою їм рівних немає.
Власне, почав читати “Записки из полполья” Достоєвського. І побачив багато спільного у своїх думках та виводах ГГ. Мені вистачило перших 8 глав, щоб повністю поставити під сумнів свої дії та спосіб мислення, думаю, далі буде не менш цікаво.
Якщо ви нормально відноситесь до творів, в яких на публіку показується вся прихована сутність людини, то також можете почитати. Свобода, лінь, бажання дурного, насолода від приниження та болю, самообмеження, верхній прояв егоїзму та завищеного ЧСВ, і все це придобрено професіональним текстом у вигляді сповіді. Must read, якщо вам це знайомо.
Success story: novice haskeller moves console cursor with “netwire” FRP library
Hey, I’m definitely novice haskeller (just to point out: couple of weeks ago I’ve had understood State monad and monad transformers, before that I lived without state for almost 6 months), so I think it is just time to jump into FRP. Men say “FRP is cool” and “FRP is not slow” and “Conal Elliot, whah! beg beg beg”, so I, an novice hero have to tame that beast while the programming world starts to shift to Haskell!
So, what library for FRP sholud I use? Hm, lets examine Hackage…
Hackage: FRP
Ouch. Large choice make me cry ‘( Need something else.
StackOverflow: What’s the status of current Functional Reactive Programming implementations?
Okay, so reactive-banana or netwire. Seems like installing netwire will be faster, because typing cabal install netwire is faster than cabal install reactive-banana.
Installed
Me, remind myself, why do I need FRP? Oh, yes, I’ve recently done a dozen of variations of console tetris and have not forgot all the pain for dealing with timers, state, input, game loop and laziness. I am adequate and I don’t want to rewrite that fully with such a new thing for me as FRP. So let’s take a small subtask – move console cursor on screen with keyboard (discovered System.Console.ANSI recently) – and do that right way!
Let’s start with reading documentation. Hm… Sure… Wow… Ehh… Aha, here it is, an Example:
module Main where import Control.Monad.Identity (Identity) import Control.Wire import Prelude hiding ((.), id) import Text.Printf testApp :: Wire () Identity a Time testApp = timeFrom 10 main :: IO () main = loop testApp clockSession where loop w' session' = do (mx, w, session) <- stepSessionP w' session' () case mx of Left ex -> putStrLn ("Inhibited: " ++ show ex) Right x -> putStrLn ("Produced: " ++ show x) loop w session
Runnig this we have:
Produced: 12.8301619000001 Produced: 12.8371623000001 Produced: 12.844162700000101 Produced: 12.849163000000102 Produced: 12.854163200000102 Produced: 12.860163600000101 Produced: 12.866163900000101 Produced: 12.871164200000102 Produced: 12.880164700000103 Produced: 12.885165000000104 Produced: 12.893165500000103 Produced: 12.900165900000104 Produced: 12.905166200000105 Produced: 12.912166600000106 Produced: 12.918166900000106 Produced: 12.924167300000105
Wow! But what changes should I do, to stop it printing after e.g. 15 seconds? Diving to hackage docs… wackelkontakt wtf?!.. Accum… Analyze… Effect… It is definitely hard to find something, for which you do not know the name… Maybe that – when? Let’s try. Other examples combine wires with composition, so do I:
testApp :: Wire () Identity a Time testApp = when (< 15) . timeFrom 10
Produced: 14.961283800000212 Produced: 14.968284200000213 Produced: 14.974284500000213 Produced: 14.982285000000212 Produced: 14.987285300000213 Produced: 14.993285600000213 Inhibited: () Inhibited: () Inhibited: () Inhibited: () Inhibited: ()
Cool! Not what I wanted, but output changed after 15 seconds. Win.
Reality
After examining example a bit carefully, I see the global loop. Documentation says nothing about neediness of global loop in wire program, so I can only assume – it is necessary. To make things simpler, let’s abstract from it:
control whenInhibited whenProduced wire = loop wire clockSession where loop w' session' = do (mx, w, session) <- stepSessionP w' session' () case mx of Left ex -> whenInhibited ex Right x -> whenProduced x loop w session
Now we can run wires like this:
main = control return (putStrLn . show) $ when (< 15) . timeFrom 10
14.966284000000254 14.969284200000255 14.974284500000255 14.978284700000255 14.981284900000256 14.985285100000256 14.990285400000257 14.995285700000258 14.999285900000258 Interrupted. *Main>
Do you see it? The output has stopped printing after 15 seconds gone! Great discovery, this will help me later, no doubt.
Keyboard
Dealing with console keyboard is not a hard task for a man, who wrote console tetris. Let’s reuse our keyPressed :: IO (Maybe Char)
foreign import ccall unsafe "conio.h getch" c_getch :: IO Char foreign import ccall unsafe "conio.h kbhit" c_kbhit :: IO Bool keyPressed = do isKey <- c_kbhit if isKey then Just <$> c_getch else return Nothing
Now we have to create wire from that function. How? Let’s read documentation. Hm.. Hm.. Hm-hm. As I see, the only wire creators are in Control.Wire.Wire, and start with “mk“. As I see, dealing with IO effects can only be done via mkGen (but I’m not that sure)
mkGen :: (Time -> a -> m (Either e b, Wire e m a b)) -> Wire e m a bSource Construct an effectful wire from the given function.
Ok, maybe this way?
pressedKeyMaybe = mkGen isKey where isKey time () = do ky <- keyPressed return (Right ky, pressedKeyMaybe) -- strange, why do I use same -- function in the snd of the tuple?
Let’s test it:
main = control return (putStrLn . show) $ pressedKeyMaybe
Ha-ha! It doesn’t compile. Warum?
Couldn't match expected type `Identity' with actual type `IO'
Expected type: Wire () Identity () b0
Actual type: Wire () IO () (Maybe Char)
In the second argument of `($)', namely `pressedKeyMaybe'
In the expression:
control return (putStrLn . show) $ pressedKeyMaybe
Failed, modules loaded: none.
Prelude>
I’m lucky and this problem went off with simple replace stepSessionP to stepSession in control function.
Nothing Nothing Just 'j' Nothing Nothing Nothing Nothing Nothing Just 'k' Nothing Nothing Nothing Nothing Nothing Nothing Nothing
Just to make it less verbose:
main = control return (putStrLn . show) $ when (/= Nothing) . pressedKeyMaybe
*Main> main Just 'h' Just 'e' Just 'l' Just 'l' Just 'o' Just ' ' Just 'n' Just 'e' Just 't' Just 'w' Just 'i' Just 'r' Just 'e' Interrupted. *Main>
It’s great time I live in, excuse me for my bad English and egocentrism.
(BTW, I give thanks to netwire author for the “wackelkontakt” mention in the doc. Because it is in fact an effectfull wire, so it can be used as example implementation:
wackelkontaktM :: (MonadRandom m, Monoid e) => Double -- ^ Occurrence probability. -> Event e m a wackelkontaktM p = mkFixM $ \_ x -> do e <- getRandom return (if (e < p) then Right x else Left mempty)
So my key stroke wire looks like this now:
pressedKeyMaybe = mkFixM $ \_ _ -> do ky <- keyPressed return (Right ky)
)
Counting keypresses
Next small subtask in my great mission is to count keystrokes. I know already that FRP is about accumulating events or smth like that, so I want to use accum wire for this task. My plan is:
countingWire = accum (+) 0 . when (/= Nothing) . pressedKeyMayb
That doesn’t work, because Maybes do not accumulate with (+). We have to convert them to integers:
toInt :: Int -> Wire m e a Int toInt v = mkPure (\_ _ -> (Right v, toInt v)) countingWire = accum (+) 0 . toInt 1 . when (/= Nothing) . pressedKeyMaybe main = control return (putStrLn . show) countingWire
(…pressing some keys, while running wire…)
*Main> main 0 1 2 3 4 5 6 7 8 9 Interrupted. *Main>
Astonishing result! But we can do better, because of <|> operator. As I’ve understood, it looks like “or”, so we can have power:
countingWire = accum (+) 0 . (toInt (-1) . when (== Just 'h') <|> toInt 1 . when (== Just 'l')) . pressedKeyMaybe
But when we have power, we want more power! Let’s control two values simulationously:
cursorWire = accum (\(a,b) (c,d) -> (a+c, b+d)) (0,0) . ( pure ((-1), 0 ) . when (== Just 'h') <|> pure ( 1, 0 ) . when (== Just 'l') <|> pure ( 0, (-1)) . when (== Just 'j') <|> pure ( 0, 1 ) . when (== Just 'k') ) . pressedKeyMaybe
(Have you noticed, how toInt was replaced with pure? That was an “Aha” moment when studying wires)
*Main> main (0,0) (1,0) (2,0) (3,0) (3,1) (3,0) (3,-1) (2,-1) (1,-1) (0,-1) Interrupted. *Main>
Result
Now I am not that novice, that I was in the beginning, I am netwire-non-novice! Creating the cursor moving wire is now easy:
moveCursor = mkFixM $ \_ coords@(x,y) -> setCursorPosition y x >> return (Right coords)
And the result is:
main = control return (const (return ())) $ moveCursor . cursorWire
(Sorry, it's hard to show how I control the cursor with "hjkl" keys, simply believe me or try yourself)
I am pretty pleasant, that while we work with changing values, the whole system stays composable. I don’t know how to develop large systems with FRP architecure, but this small practical exercise gave me more than blind reading tons of reddit articles.
By the way, thanks for reading!
Post Scriptum
Have you ever noticed, that Windows console sends TWO keystrokes, when pressing arrow keys? Yeap, and if you worked with it, you may think it is headache with FRP. Suprisingly, no, with netwire it is simply a couple of compositions:
. edge ((/=) 224 . fromEnum . fromMaybe ' ') .
Putting this code before pressedKeyMaybe in wire chain will filter only those intents, who had keycode 224 just a moment ago.
cursorWire = accum (\(a,b) (c,d) -> (a+c, b+d)) (0,0) . ( pure ((-1), 0 ) . when (== Just 'K') <|> pure ( 1, 0 ) . when (== Just 'M') <|> pure ( 0, (-1)) . when (== Just 'H') <|> pure ( 0, 1 ) . when (== Just 'P') ) . edge ((/=) 224 . fromEnum . fromMaybe ' ') . pressedKeyMaybe
Whole code – Github Gist: moving console cursor
Hometask
One more thing I’ve left to readers. Limit the cursor movement to screen rectanlge. So that we will not exit the screen area.
Про порядок
Зараз я буду говорити доволі дивні і дещо очевидні речі, тож не викликайте в коментах, будь-ласка, капітана “О”, однак сприймайте інформацію критично.
Пошук
Однією з проблем у людському (конкретно, моєму) житті є пошук. Не НАЙважливіша, і навіть не критична. Однак занадто часто зустрічається, щоб її ігнорувати. Питання, котре спровокувало цей хід думок, було доволі невинне: “Як знайти лева в пустелі, якщо немає гелікоптера?”. Розширимо його тепер далі. Як знайти однодумців навколо себе, якщо навіть у всьому світі їх маленьке число? Як знайти дівчину, якщо ти асоціяльний? Як знайти цікаву книгу, якщо не знаєш що саме тебе цікавить? Як знайти кімнату в Києві, якщо тобі потрібна окрема квартира, ні з ким не ділити, дешева та з євроремонтом? Як знайти роботу на С++ на 1000$, якщо у тебе нульовий досвід роботи? Як знайти у собі сили вийти за межі комфорту, якщо й так все влаштовує? Як знайти найкращу політичну партію, якщо всі вони по дефолту однаково погані?
І що цікаво, жодна з проблем пошуку не є перешкодою для (мого) щасливого життя (хоча бувають і серйозні проблеми пошуку, не сперечаюсь). Тобто, невирішені проблеми пошуку – це всього-лиш примарні можливості, а не відкати існуючого. Але я все-таки називаю їх “проблемами”, бо ці питання катують моє Его і дають причину сумніватись у моїх здібностях до “життя”. А тут вже пахне суїцидом, чи не так? =)
Порядок
Одним з найвідоміших алгоритмів пошуку є так-званий “бінарний пошук”. Для того щоб він спрацював, потрібно або наявність відсортованого масиву ключів, або наявність додаткового бінарного дерева пошуку ключів. Якщо такої додаткової структури немає, то доводиться використовувати пошук типу “повний перебір” або “метод випадкового вибору елементу”, з падінням швикдості пошуку на порядки. З цього незамислуватого факту ми можемо видерти наступну інформацію: пошук в організованому середовищі (даних) має більше шансів бути успішним і швидким, ніж пошук в неорганізованому середовищі (даних).
Оскільки я люблю сильно узагальнювати, то останню фразу я переінакшив на свій лад: якщо виконати глобальну організацію існуючої інформації та розробити правильний та швидкий алгоритм доступу до цієї організованої інформації, то це буде краще, ніж залишити все в неорганізованому стані. Власне, це і є та ідеологічна основа Істини, Прогресу та Порядку, про котрі я писав у “пробудженні”. І також зрозуміло, чому Еліта являється сектою – бо у Еліти ця фраза піднята до апофеозу абсурду: людина існує або створена тільки для того, щоб робити навколо себе Порядок.
Порядок в голові
Абзацом вище я визначив мету Еліти як “апофеоз абсурду”. Виходить, я сам собі протирічу і висуваю одначасно абсолютно протилежні думки (“порядок” – “абсурд”). А якщо я протирічу, то дуже вірогідно що я приймаю неправильне рішення. Підемо далі – з чого це я взяв, що я приймаю правильні рішення? З якого це дива моя думка повинна впливати на світ? (Або навіть радикальний варіянт – якого хєра я повинен виконувати свої бажання, примхи, захцянки та обов’язки?) Безлад у голові – ось як це називається, і всі прийняті рішення та висказані думки в період “безладу” будуть, ймовірно, безглуздими та неправильними.
Тому, якщо тобі важливо зменшити кількість неправильних рішень (навіть якщо ти не можеш перевірити рішення на “правильність”), то достатньо робити їх у стані “порядку в голові”. А оскільки рішення приймаються доволі часто, то бажано не виходити з цього стану. Як досягається порядок в голові? Дуже просто. Зберігати в голові тільки впорядковану інформацію, відповідно до неї формувати свої думки. А як отримати цю впорядковану інформацію? Ну як же, попросити у Еліти, жреців Порядку!
Істина
Доволі специфічною і цікавою властивістю порядку є консистентність та однорідність. А оскільки брехня по визначенню не може бути консистентною, значить брехня до порядку не відноситься. Відповідно, неправдивість та брехливість не можуть бути властивостями впорядкованої інформації.
А отже, ми можемо тепер сформулювати термін “впорядкованість” у більш-менш зрозумілому вигляді: порядок – це стан інформації та речей, в якому інформація є істинною (правдивою) та пошук відбувається швидше, ніж в будь-якому іншому стані.
Післяслово
Я думаю, викладена вище думка має право на процвітання хоча б тому, що весь світ уже рухається в напрямку порядку, а після Ніцше з’явилось дуже багато підтверджень, що порядок в голові можна виділити як окрему сутність. Також, на рахунок питань у розділі про пошук – усі вони наразі акутальні та “життєві”. Для їх вирішення я не зможу використати ні Еліту (бо її немає), ні Порядок (бо його ніхто ще не створив), ні порядок в голові. Хоча брешу, стан “порядку в голові” можна плекати в собі прямо уже, і він справді допомагає не втрачати розум у нашому жорстокому світі (до-речі, завидую тим прагматикам, у котрих він в голові з дитинства). Ось.
Пробудження
Зв’язок між назвою посту і Сіддхартою Гаутамою не випадковий, бо саме так я відчув себе після однієї розмови з дівчатами. Заговорюючи їм мізки всякою лабудою, я випадково сформулював думку всього свого свідомого життя. Вона виявилась настільки простою та суперечливою, що легко може стати в основі постмодерної релігї (або хоча б світогляду). Ну що, чи цікаво вам послухати далі, чи закриєте сайтик від гріха подалі?
Істина, Прогрес, Порядок, Ідея, Еліта, Інструмент
Отже, я перечислив 6 ключових слів цього світогляду. Давайте я поясню зв’язок
- Основою буття є пошук, чи то ба прямування до Істини. У цьому плані моя думка не зайшла далі за об’єктивний ідеалізм. Щоправда, я допускаю, що у кожного своя Істина, і шлях до Істини у кожного свій. Важлива саме наявність цього прямування, прямування до чогось хорошого, ідейного, теплого, приємного та важливого.
- Я не бачу прямування до Істини без Прогресу. Накопичення знань про світ ІМХО неможливе без їх розширення. Тому будь-які обмеження по розвитку повинні бути добре обгрунтовані у світлі руху до Істини. Фактично, цим самим я признаю науковий метод як основний метод пізнання світу, але не виключаю використання інших, лиш би не було проблем (адже проблеми – це шлях ВІД Істини)
- Люди постійно приймають певні рішення, виконують вибір. Але хіба їх цьому вчать в школі? Ні, через це виходить що одні люди живуть добре і не паряться (бо приймають правильні рішення), а інші бідкаються та виживають а не живуть. Фактично, ця соціонерівновага залежить від однієї складної штуки, котру я називаю “Порядок в голові”. Згідно моєї ідеології, прийняття рішень без Порядку в голові є доволі непевною штукою, бо наслідки важко прогнозуються. А як без цього Порядку дійти до Істини? Тому тема Порядку (в першу чергу, Порядку в голові) повинна стати ключовою в моїй секті іновірців. (Якщо вас цікавить, що таке “Порядок в голові”, то це такий стан людини, коли вона може приймати правильні та обгрунтовані рішення)
- Люди без Ідеї – також люди, але вони ближчі до тварин, а не людей
Об’єднання потягу до Істини, Прогресу та Порядку відбувається від егідою “Ідеї”. Проте ось яка цікава штука: я стверджую, що ця “ідея об’єднання” не формулююється словами! Чому? Тому-що точне визначення призведе до дуже серйозних “ідейних” проблем (наприклад необхідність зміни, відповідність моралі, доведення непротирічності). Більше того, я стверджую: в самій Ідеї закладено її безсмертя! Тобто, сама Ідея повинна слугувати не тільки на досягнення Істини, Прогресу та Порядку, а ще й на виживання самої себе. Це зроблено на випадок, якщо деякі конспірологічні теорії не брешуть, і знайдуться люди, котрим на руку буде похоронити таку хорошу “Ідею”. - Еліта – це сукупна назва людей (та угруповань людей), котрі сповідують описані вище принципи і забезпечують виживання Ідеї. Кожен, хто має спрямування до Істини, вірить у Прогрес та наводить Порядок у своїй голові, автоматично перетворюється у Еліту. А серед тієї еліти суспільства, котра є зараз, не кожен є Елітою в моєму формулюванні. Еліта діє по законам тієї держави, в котрій живе, але разом з тим між окремими Еліта діють внутрішні зобов’язання, дещо схожі на соціалістичні (ну як євреї, ви зрозуміли). Еліта намагаються допомагати один одному, але не стільки матеріально, скільки ідеологічно. Чисто як в приказці, не віддати свій улов іншому рибалці, а навчити правильно ловити рибу.
- Оскільки ми живемо в епоху небувалої доступності інформації, у нас з’являються всі шанси розширити слабкий людський розум за допомогою зовнішніх електронних пристроїв. Еліта утворює поміж себе спеціальну мережу, яка буде слугувати прообразом тієї сутності, котра повинна пізнати Істину (людина не може її пізнати), навести Порядок в її “голові” (це я називаю Порядком в інформаційній сфері) та використовувати її для наведення Порядку у власних головах (та в головах інших, чому б і ні). Вся ця цифрова та електронна система штучного розуму і називається Інструмент. (В дечому схоже на вікіпедію, але з більш глобальною інфраструктурою, включаючи цифрові переносні пристрої та наново переписану всю цифрову обв’язку). Інструмент також повинен виконати ще одну, надзвичайно важливу функцію, – спростити навчання, бо без швидкого навчання досягнути швидкого прогресу неможливо.
Секта
Я вважаю, що кожна звичайна людина вправі робити те, що вона вважає правильним для себе (тобто, потакати своїм бажанням). Тому слідування викладеному вище світогляду робить людину “незвичайною”, або навіть “ненормальною”. Оскільки рівень проникання Ідеї в мізки доволі високий, а відсоток масовості доволі низький, то назвати це “субкультурою” або “культурою” буде важко. Найкраще тут підходять визначення Елітаризму як псевдо-релігії або науково-технічної секти.
Проблемка
Одна проблемка. Для заснування секти потрібні люди, а де ж взяти таких же психанутих, як я? А все просто. Потрібно скласти певний базовий устав, певний базовий Інструмент, щоб було зрозуміло, що і в яких випадках робити. Потрібно організувати проповіді (а отже, придумати зачіпаючі за душу тексти). Потрібно знайти місця скупчення цільової аудиторії (до котрої, до-речі, пред’явлено доволі високі запити). І, що найголовніше, треба своєю поведінкою показати людям приклад!
І кожна з цих маленьких проблемок є дуже складною проблемою, хоча і набагато простішою, ніж “мета на тисячу-тисяч життів”.
PS. По ходу діла доведеться вичитати Ніцше, Майн Кампф та ще багато-багато цікавих книжок. Ох, як я не люблю філософію та історію…
Про релігію
Йшов по дорозі та подумав ось про яку філософську-психологічну дурничку.
Іноді я про себе промовляв скорочену версію молитви Ісуса — “Ісусе Христе, Сине Божий помилуй нас, грішних”. По кілька разів (один попутній священник навчив). Так ось, після цього моє серце починає частіше битись, і я відчуваю тепло по тілу! Таке враження, що й справді Ісус “входить в серце” і допоможе в моїх майбутніх справах.
Про схожу поведінку людини (ось це тепле відчуття) мені розповідали кілька глибоко віруючих, з якими я мав нагоду спілкуватись. Фактично, вони розвивали цю тему далі — “Ми не йдемо широкою дорогою, ми йдемо вузькою”, “Доки ти не спробував прочитати і зрозуміти Біблію, ти не зможеш оцінити нашого щастя”, “Після Страшного Суду ми потрапимо в рай і будемо жити вічно та безбідно”.
Я зрозумів — кожна така фраза викликає це тепло в тілі та відчуття чогось неземного, і в цьому суть філософської дрібнички. А в чому суть психологічної?
А в тому, що це відчуття тепла — таке саме, як коли брешеш. Комусь або собі. Нагло брешеш. Несеш таку брехню, від якої аж мурашки по шкірі. Сам же розумієш, яка нісенітниця, але потім і звикаєш до неї. Промовляєш якусь чепухню, а про себе думаєш “Який же я молодець, що ЗМІГ цю чепухню прийняти в реальність, що не кожному дано”.
Найкраще цей прийом працює на дітях. Адже для них висловлювання дорослих часто є щирими істинами (згадаємо того ж Святого Миколая). Віруючі батьки змалку вбивають доволі суперечливу “правду” і діти виростають із збоченням психіки під назвою “релігійна віра”. Зустрілись мені 2 жінки, одна стара, друга десь 18 років та вагітна. І якщо по старенькій було видно, що до Біблії та Бога вона прийшла своїми силами (читай, сама вибрала шлях брехні та це роузміє), то молода була просто фанатичкою, з нападками та наказовим тоном.
Уяснивши собі цей момент, я тепер шукаю інші випадки, коли люди зумисне собі брешуть. Складаєть враження, що без самобрехні людина вижити не зможе, бо сувора дійсність світу без захисного бар’єру калічить психіку аж до самогубства. Самобрехня — це частина нашого життя, і її усвідомлення зробить нас сильнішими людьми, якими й стали глибоко віруючі.
NixOS – дистрибутив Лінукса з декларативною конфігурацією
Блукаючи по сторінках, пов’язаних з Haskell-ем, наштовхнувся на експериментальний дистрибутив Лінукса з цікавими властивостями. Особливо мене зацікавили наступні: можливість транзакційного оновлення системи та декларативне налаштування системи. Першими словами було “Це анріал”, але дистрибутив скачав і поставив (у мене якраз ArchLinux здох через апдейт драйверів). Певні враження та приклади приведу в даному пості.
Конфігурація NixOS
Менйтейнери знають про функ. програмування не з лівих вуст – головний девелопер вчився в Utrecht University, котрий подарував світу альтернативну GHC реалізацію Хаскеля. Власне він розробив спеціальну функційну мову для роботи з пакетами, в якій установка пакета або вибір певної поширеної опції є всього лиш “галочкою” у текстовому вигляді. Ось приклад, що я вже наставив собі у систему (файл /etc/nixos/configuration.conf):
# the system. Help is available in the configuration.nix(5) man page # or the NixOS manual available on virtual console 8 (Alt+F8). { config, pkgs, ... }: { require = [ # Include the results of the hardware scan. ./hardware-configuration.nix ]; boot.initrd.kernelModules = [ # Specify all kernel modules that are necessary for mounting the root # filesystem. # "xfs" "ata_piix" ]; # Use the GRUB 2 boot loader. boot.loader.grub.enable = true; boot.loader.grub.version = 2; # Define on which hard drive you want to install Grub. boot.loader.grub.device = "/dev/sda"; boot.initrd.enableSplashScreen = true; networking.hostName = "localhosted"; # Define your hostname. networking.wireless.enable = true; # Enables Wireless. networking.useDHCP = true; networking.wireless.driver = "wext"; # networking.wireless.userControlled.enable = true; networking.nameservers = [ "8.8.8.8" ]; # Add filesystem entries for each partition that you want to see # mounted at boot time. This should include at least the root # filesystem. fileSystems = [ { mountPoint = "/"; device = "/dev/disk/by-label/nixos"; } { mountPoint = "/storage"; # where you want to mount the device device = "/dev/sda8"; # the device fsType = "ext4"; # the type of the partition # options = "data=journal"; } ]; # List swap partitions activated at boot time. swapDevices = [ { device = "/dev/disk/by-label/swap"; } ]; # Select internationalisation properties. i18n = { consoleFont = "Terminus"; consoleKeyMap = "us"; defaultLocale = "uk_UA.UTF-8"; supportedLocales = [ "en_US.UTF-8/UTF-8" "ru_RU.UTF-8/UTF-8" "uk_UA.UTF-8/UTF-8" ]; }; # List services that you want to enable: # Enable the OpenSSH daemon. # services.openssh.enable = true; # Enable CUPS to print documents. # services.printing.enable = true; # Enable the X11 windowing system. services.xserver.enable = true; services.xserver.layout = "us"; services.xserver.xkbOptions = "eurosign:e"; # Enable the KDE Desktop Environment. services.xserver.displayManager.kdm.enable = true; services.xserver.desktopManager.kde4.enable = true; }
Гляньте, для прикладу, на рядок
# Use the GRUB 2 boot loader.
boot.loader.grub.enable = true;
boot.loader.grub.version = 2;
# Define on which hard drive you want to install Grub.
boot.loader.grub.device = "/dev/sda";
Достатньо всього лиш вписати в конфігу ці рядки і після ребілду система сама поставить GRUB куди їй там треба. Якщо ми змінимо версію на 1, то під-час ребілду буде автоматично скачаний перший груб і поставлений замість другого. Тобто, не обов’язково вручну скачувати, ставити і налаштовувати пакет, якщо для нього є описана опція – користуйтесь нею.
Інший приклад – мережа.
networking.hostName = "localhosted"; # Define your hostname.
networking.wireless.enable = true; # Enables Wireless.
networking.useDHCP = true;
networking.wireless.driver = "wext";
# networking.wireless.userControlled.enable = true;
networking.nameservers = [ "8.8.8.8" ];
В ArchLinux також спробували зробити такий конфіг мережі, але у них вийшло гавняно і новий синтаксис використовується не сильно. (До-речі, якщо вам вкрай не подобається вручну налаштовувати мережу, можна все то закоментувати і вказати опцію networking.wicd.enable=true; – всі налаштування будуть йти через wicd, котрий буде автоматично скачаний і поставлений.)
Далі ви побачите статичне монтування дисків. Замість спеціального синтаксису mount і /etc/fstab можна використовувати уніфікований, що є серйозним напрямком до User-oriented операційної системи.
Установка X і кедів – взагалі пісня. Простіше, мабуть, тільки галочка (але які нафіг галочки, коли навіть xserver не стоїть?)
Модифікація системи
Серед різноміїття дистрибутивів я надаю перевагу rolling-release варіаціям. Мені подобається, коли система оновлена “по максимуму”. Проте вибір, як такий, у мене невеликий – Arch Linux, Gentoo, NixOS, можливо ще щось. Арч при цьому має перевгу над Генту в плані наявності бінарних пакетів. Так ось, NixOS також має таку перевагу над Генту! Тобто, всі програми компіляться з сорсів, проте якщо є бінарник, то буде використаний саме він. Бінарники, в свою чергу, білдяться на спеціальній гідра-фермі, цілодобово, кожен день.
Всі пакети ставляться в юзер-спейс, тому для установки пакетів не потрібно root-права. Я поки-що не знаю, добре це чи погано, але це чудова причина для тих, хто юзає sudo без пароля, використовувати менше sudo без пароля.
Іншою цікавою особливістю є те, що якщо конфігурація системи змінюється, то це не призведе до втрати системи. Усі старі кофігурації не видаляються, і потім доступні у меню GRUB. Цим самим можна легко відкатити будь-які зміни системи, що радує (fallback image не завжди справляється з цією задачею, адже проблеми можуть бути в обв’язці ядра, що враховується конфігурацією NixOS.)
Миска дьогтю
Недарма NixOS виступає як експериментальна система. Один з Дебіан-майнтейнерів розкритикував її менеджер пакетів, коли хтось запропнував замінити dpkg і apt.
- Готових пакетів доволі мало. В тому ж дебіану, генті та арчі їх в рази більше
- Готових бінарних пакетів дуже мало. Навіть Chromium, і той поки-що не білдиться бінарно (а на моєму компі він компілився всю ніч, поки не здохла батарея…)
- Замість dependency hell ми тепер отримуємо configuration options hell. Кількість опцій зашкалює і в майбутньому буде тільки рости. Думаю, скоро з’явиться якась графічна приблуда для цих опцій і ми отримаємо повноцінний MacOS *irony*
- Якщо ви будете модифікувати систему старим способом (через конфіги /etc), то будьте готові ними пожертувати під-час чергового ребілду системи. Тру-способом є створення нової опції і підключення її в конфіг системи. Unix-way, кажете? =)
- Все тормозить. В порівнянні з Arch
- Ггюки присутні. Наприклад, kate під рутом не хоче запускатись
- Думаю, проблема з драйверами для відеокарти тут стоїть дуже гостро.
- Відсутність крауд-документації (гугл-отвєти, форуми, ком’юніті). Всі проблеми треба напряму вирішувати з девелоперами або в не дуже активному IRC-чаті (котрий, до-речі, мені сильно допоміг в установці системи). Гугл і ЛОР практично нічого не знають про NixOS.
- Думаю, тут є серйозні проблеми з security, але це всього-лиш здогадка
- В папці /bin знаходиться тільки один файл – /bin/sh ! kernel panic!
- А знаєте, чого один? Бо всі програми ставляться в /nix/store/, і до них спереду дописується 160бітний хеш. Вигляда
це приблизно так:[danbst@localhosted:~]$ ls /nix/store/ | head 00418vqs74568frbp4bzb27wq0q7drd2-switch-to-configuration.sh 00ix198j4hfbbkb2w18aiwiz73rr017l-libevent-2.0.17-stable.tar.gz.drv 00j4kxw914408w5bwap970f7iljff955-upstart-tty4.conf 01656gv7ysz8vj6qxnh5drqka6n1118n-closure.drv 01a60wl2a2gg7pwcs7v4arx29173s9g3-kde.pam.drv 01d61sgqavjph83c9qx9n05yjv1qblcl-gmp-5.0.5 01dzpp03fsvc7xixg9sp2hdhi5iy9m3v-1k7b4mpfxpbcq5wih614z9g05w22ibwjq8wzh30xihl70q310zzq.nar-bsdiff 01r9wqssr0m7h7hb13jkchn3g8zyjk6r-pth-2.0.7 01w11lngp8s4lxllyr6xbmjfyrfkrn43-builder.sh 0286r8g56dj2pvirgac079flz9gjnka2-font-cursor-misc-1.0.3.tar.bz2.drv
ЖОПА. Переходити по автокомпліту до потрібного пакету тепер нереально.
- …і ще багато-багато різних голімих приколів, котрі мене поки-що не чіпали.
Як висновок
Як висновок, я скажу, що система юзабельна в певних межах і цікава, в межах своїх унікальних властивостей. Як на робочу машину я би не ставив, але на домашню типового лінуксойда – то цілком навіть дуже непогано. Подивимся, чи зможу я на ній прожити до кінця літа, поставити xmonad, ATI-драйвери, wine та Windows-ігри. Якщо особливо серйозних проблем я з цим не отримаю, то значить WIN. Арівідерчі!
Про тетріс, Хаскель і типи
Писав я недавно один проект на Python і зрозумів – відсутність типів у пайтончику заважає розбиратись у чужому коді. Щоб взнати, який тип має змінна доводилось вписувати print(var) і перезапускати програму (а вона була графічна). Це не особливо напрягало, проте тепер я розумію, чому вказувати типи потрібно навіть в “очевидних” випадках – не всім воно очевидно.
З такою логікою я взявся і дописав типи до більшості функцій свого тетріса на Хаскелі. Заодно перейшов від чисто функціонального програмування до типо-функціонального (тут немає помилки в слові “типо”).
https://gist.github.com/2281537 - для тих, хто спершу дивиться код.
Ну, по-перше, код збільшився на 45 рядків. Печалька… По-друге, в намаганнях зробити кросплатформенність я так нічого і не досяг, для запуску коду під лінуксом потрібно модифікувати код. Печалька :`( …
Випадковий елемент
> randomItem xs = (xs!!) <$> randomRIO (0, length xs - 1)
Навчу вас читати цей код. Першим ділом дивіться на символ <$>. Це потужний символ, він розділяє вираз на дві частини – ліву і праву. Все, що зліва – функція. Все, що справа – дані. Символ <$> застосовує функцію зліва до даних справа.
Чому саме так? Про це дещо нижче. Глянемо тепер на ліву частину – функцію. Символ !! еквівалентний отриманню елементу по індексу. Те, що в нормальних мовах xs[2] в Хаскелі записується як xs !! 2. Якщо xs – список, то xs !! 2 поверне елемент цього списку. А чому дорівнює xs !! ? Це уже буде функція, що приймає на вхід індекс а повертає елемент списку! Тому запис (xs!!) еквівалентний (\index -> xs !! index), тільки коротший.
Справа знаходяться дані, що генерує функція randomRIO. Вона повертає випадкове число з інтервалу, вказаного у дужках. Єдина проблема – це випадкове число загорнуто у монаду IO, з якої, як відомо, ніщо не може вийти.
Тепер ще раз про <$>. Цей оператор вводить функцію зліва в монаду IO справа і спокійно застосовує функцію до даних. Загальний результат залишиться в монаді IO, що, в принципі, також непогано.
> randomItem xs = randomRIO (0, length xs - 1) >>= (\index -> return (xs !! index))
Ось так в коді виглядає те, що я описав. Але всі пишуть через <$>, бо так простіше.
Таймер
> -- | Таймер для відмірювання одного інтервалу > type Timer = ( Rational -- ^ показник часу на початок відмірювання > , Rational) -- ^ інтервал відмірювання (в секундах) > -- | Створити новий таймер > createTimer :: Rational -- ^ Інтервал часу для таймера (в секундах) > -> IO Timer > -- | Оновити стан таймера. Друге поле кортежу результату показує, > -- чи прошов вказаний у таймері інтервал часу > updateTimer :: Timer -> IO (Timer, Bool)
Об’ява такого типу дещо спростила роботу з часом за рахунок інкапсуляції коду. Тепер мені не доводиться заморочуватись параметрами таймера при передачі часу в mainLoop, що радує. Також зауважте спосіб анотації параметрів.
Таймер використовується, наприклад, у main:
> main = do timer <- createTimer 1 > score <- gameLoop (World (emptyScreen 10 12) Nothing) 0 timer > putStrLn ("Your score - " ++ (show score))
Світ
> data World = World [[Char]] -- ^ поле гри > (Maybe ( Int -- ^ координата X фігури > , Int -- ^ координата Y фігури > , [[Char]])) -- ^ геометрія фігури
Стан світу мені довелося загорнути в окремий тип даних. Якщо чесно, це тільки ускладнило все. Наприклад, поворот фігури в світі тепер займає у 4(!) рази більше коду, ніж раніше.
було
> rotateFigure (screen, (x, y, fig)) = (screen, (x, y, rotate fig))
стало
> -- | Поворот фігури проти годинникової > rotateFigure :: World -> World > rotateFigure (World screen figure) = World screen (rot <$> figure) > where rot (x, y, fig) = (x, y, rotate fig)
Ось так ось… Але чого не зробиш заради читабельності! До-речі, бачите знову цей символ <$> ? На цей раз він працює не з монадою IO, а з монадою Maybe. В загальному, він працює з будь-яким аплікативним функтором, а оскільки монади являються функторами, то символ працює і з будь-якими монадами. Для списку він навіть має окрему загальноприйняту назву – map (девелопери спеціально залишили цю застарівшу назву map для того, щоб зробити помилки компіляції більш зрозумілими, коли справа йде про списки – все для новачків!)
Наслідки типізації
> -- | Визначити, які рухи довзолено фігурі в заданій позиції > findPossibleMoves :: World > -> ( Bool -- ^ рух вправо > , Bool -- ^ рух вліво > , Bool -- ^ рух вниз > , Bool) -- ^ поворот проти годинникової > -- | Об'єднує поле з фігурою та викидує заповнені рядки. > removeFilledLines :: World > -> ( [[Char]] -- ^ об'єднане з фігурою поле гри > , Int) -- ^ кількість заповнених та видалених рядків > gameLoop :: World -- ^ Стан гри > -> Int -- ^ Рахунок > -> Timer -- ^ Таймер, що відмірює час падіння фігури на один блок вниз > -> IO Int
Як глобальним наслідком тотальної типізації стала … поява коментарів до параметрів функцій! Якщо не впадлу написати тип параметру, то вже й не впадлу написати комент до типу. Хоча, якщо порівняти gameLoop з попепередньою версією:
> gameLoop :: ([[Char]], (Int, Int, [[Char]])) -- ^ світ > -> Int -- ^ рахунок (скільки ліній заповнено) > -> Bool -- ^ необхідність рефреша світу > -> (Rational, Rational) -- ^ стан часу > -> IO ()
То прогрес як на долоні. Якщо постаратись, то за допомогою лінз та реактивного програмування можна буде добитись ООПшної лаконічності при модифікації стану гри, над чим я ще попрацюю.