MoreRSS

site iconAbdisalan MohamudModify

I like writing and sharing stuff I’m working on (when I’m not super busy).
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of Abdisalan Mohamud

Haskell Language Server on Doom Emacs

2024-11-26 22:27:22

Emacs with haskell code using the lsp to run autocomplete

Doom Emacs with the doom-one-light theme

Quick summary

  • This assumes you already have Doom Emacs installed.
  • We’ll need to install ghcup which is a Haskell toolchain.
  • And lastly we’ll make small changes to your doom config file

Install GHCup

Follow the install instructions from the official source https://www.haskell.org/ghcup/

Run the terminal user interface.

ghcup tui

Use the up and down arrows to pick your desired version and install by pressing i, then make sure to also press s after installing to “set” that version.

GHCup running in terminal ui mode

Doom init.el

Within your doom folder (usually located in ~/.doom.d/) open your init.el file. Make sure to uncomment this line and then run doom sync in your terminal to install the packages.

(haskell +lsp)    ; a language that's lazier than I am

eglot vs lsp-mode

I am familiar with eglot especially since it’s been added to emacs’ core in version 29. I’m sure the haskell language server will work with it but I haven’t put in the time to set that up and I’ve got it working with lsp-mode anyway 🤷🏾‍♂️

Doom config.el

Next open the config.el file, also located in your doom folder. We’ll be adding two snippets.

The first is setting up hooks to turn on the language server when emacs detects haskel-mode.

;; HASKELL
;; Hooks so haskell and literate haskell major modes trigger LSP setup
(add-hook 'haskell-mode-hook #'lsp)
(add-hook 'haskell-literate-mode-hook #'lsp)

Supply environment variables

If you start emacs through a GUI like me, your emacs will likely not have the same environment as your terminal. This will cause an issue where emacs cannot find the haskell-language-server program you installed with ghcup.

For example:

Failed to find a HLS version for GHC 9.4.5
Executable names we failed to find: haskell-language-server-9.4.5,haskell-language-server

This second snippet will ensure that the language server binaries will be available to emacs. It adds the ghcup bin folder to the exec-path list and the PATH environment variable. Put this code block in your config.el as well!

;; Put this in your emacs config
;; Add haskell lsp to path for emacs subprocesses
(add-to-list 'exec-path (expand-file-name "~/.ghcup/bin"))
;; Add haskell tools to path for emacs environment
(setenv "PATH" (concat (expand-file-name "~/.ghcup/bin") ":" (getenv "PATH")))

Hope this helps!

The Tragedy of Running an Old Node Project

2024-11-18 01:21:55

Sad stick figure next to gatsby logo

Updating my website

It’s been a long while since I wrote anything on this site. The framework I used was Gatsby which in 2020 was one of the hot ways of getting a good looking blog up and running quickly. With over 41 dependencies, and god knows how many more sub-dependencies, this thing was a beast.

Time to run it after not touching it for nearly 4 years.

How hard could it be to run an old node project?

Pretty hard actually.

npm install

<1000 lines of red and failures later>

Failure could not find python2...

What? Why do I need python2 to install a node package. What’s it doing? Do I have python2? There probably something wrong with this old node_modules folder. Let me delete it.

rm -rf node_modules/
npm install

...

Failure could not find python2

Okay, you win. I’ll install python2 even though this is ridiculous.

30 mintues later after fiddling with installing python2 using pyenv, hoping to not ruin my python3 setup, I finally got python2.

The complexity grows. What am I getting myself into

npm install

...

npm remove_cv_t’ is not a member of ‘std’; did you mean ‘remove_cv’

What? That looks like C, or maybe C++. Why the heck is C++ code building so that I can install some node packages that just manage basic web files.

Some Google searching and I land on a GitHub issues page for node-gyp and apparently my C++ version is below 14. That does not seem right. I’ve got to have something really wrong because I’m down the wrong path.

Then it dawns on me. Do I have the right node version or is there another one I used 4 years ago back in 2020? The only one in my node version manager is v16.

I tried doing some Googling on how to know what node version was used in a package.json. Turns out you can’t unless the project specifically sets it.. and I didn’t. Great.

Now all I’m thinking is “Please let me find this node version because I don’t want to start over with this blog site.” Then I stumble upon another GitHub issues page facing a similar C++ compile error. And they mention a broken build using v12.3 and the user should revert back to v12.2

I install node v12 and everything works.

Two hours of my life gone, just to pick up where I left off.

I’m tired and going to bed.

SQL Practice Question: Newest Dog &amp; Owner

2020-08-22 07:00:48

Photo of a cute puppy labrador with a red collar

Photo by Berkay Gumustekin on Unsplash

In the 50+ programming interviews I’ve done, I’ve only been asked two SQL questions.

I failed both of those questions.

While I won’t give the questions away, I will give you a problem to practice so that you can succeed where I failed!

This question combines many principles you’ll need to quickly solve SQL interview problems.

Hopefully you’ll be better prepared than I was 😅

The Dog Database

Imagine you’re running a dog shelter and you have a database of dogs and owners. Every dog has one owner, but owners can have many dogs.

Here’s the owner and dogs table written in PostgreSQL.

Owner Table

CREATE TABLE owners (
  id SERIAL PRIMARY KEY,
  name VARCHAR(256)
);

Dogs Table

CREATE TABLE dogs (
  id SERIAL PRIMARY KEY,
  owner_id INTEGER REFERENCES owners(id),
  breed VARCHAR(256),
  adopted_on TIMESTAMP
);

Note that there is a one to many relationship between the owners and dogs table. There is one owner id tied to each dog and enforced by a foreign key contraint i.e. you can only use owner ids that actually exist in the owners table.

The Question

Now, that the tables are setup, we can get to the real question.

Write an SQL query that gives the latest dog each owner adopted along with the name of the owner.

Pretty simple right?

Here’s some example data to help you out.

owners
 id |  name
----+--------
  1 | PersonA
  2 | PersonB

dogs
 id | owner_id |   breed   | adopted_on
----+----------+-----------+--------------
  1 |        1 | chow chow | 2019-02-03
  2 |        2 | dalmation | 2019-03-07
  3 |        2 | beagle    | 2020-09-21
  4 |        1 | pit bull  | 2020-08-01

The answer to the question should give you a result that looks like this.

✅ RESULT
name      |   breed  | adopted_on
----------+----------+------------
PersonB   | beagle   | 2020-09-21
PersonA   | pit bull | 2020-08-01

Try this out for yourself first, then I’ll go over the answer below. Don’t worry about setting this up on your computer! Here’s an SQL Fiddle (like CodePen but for SQL) for you test your answer!

http://sqlfiddle.com/#!17/5059f/10

Final Answer

Let’s go through this step by step. There’s probably a few other ways of doing this but this is mine.

Part 1: Getting the newest dogs

First we find each newest adoption date by each owner. To do this, I use the max function on the adopted_on column after grouping by owners. I make sure to also get the owner_id, that way we can use it to join on another table.

SELECT owner_id, max(adopted_on) FROM dogs GROUP BY owner_id
RESULT
owner_id |	   max
---------+--------------
       1 |	2020-09-21
       2 |	2020-08-01

Part 2: Getting the breed of the newest dogs

Next, we join the last query with the dogs table (itself) to get the breed of the dog and match by the adoption date as well as the owner.

SELECT dogs.breed, dogs.adopted_on FROM dogs
JOIN (
  SELECT owner_id, max(adopted_on) FROM dogs GROUP BY owner_id
) AS newest_dogs
ON
	dogs.owner_id = newest_dogs.owner_id AND
	dogs.adopted_on = newest_dogs.max;
RESULT
  breed    |  adopted_on 
-----------+--------------
  beagle   |  2020-09-21
  pit bull |  2020-08-01

Final: Get the names of the owners

Lastly, we join the result of the last query on the owners table to get their name.

SELECT owners.name, dogs.breed, dogs.adopted_on FROM dogs
JOIN (
  SELECT owner_id, max(adopted_on) FROM dogs GROUP BY owner_id
) AS newest_dogs
ON
  dogs.owner_id = newest_dogs.owner_id AND
  dogs.adopted_on = newest_dogs.max
JOIN
owners ON dogs.owner_id = owners.id;
✅ FINAL RESULT
name      | breed    | adopted_on
----------+----------+------------
PersonB   | beagle   | 2020-09-21
PersonA   | pit bull | 2020-08-01

Conclusion

Although the question was simple, there were a few tricky queries we had to make! We needed to join tables two times and find the max aggregate on one of the tables.

I hope you learned something from this exercise! If you want to experiment with my final answer, I’ve also included a SQL Fiddle with the final answer below.

http://sqlfiddle.com/#!17/5059f/9

Thanks for reading! If you want more content, follow me on twitter!

✌️

Why I don't Use OFFSET for Pagination

2020-06-17 07:32:59

Let’s talk about pages. You know, like this Google Search Result pages

or infinite scrolling pages like this Dev.to infinite scrolling

Because we never want to give our website visitors all of our data, we present them in pages and let users load more as they please.

One method of paging, AKA pagination, in SQL that’s easy is to limit a query to a certain number and when you want the next page, set an offset.

For example, here’s a query for the second page of a blog

SELECT * from posts
ORDER BY created_at
LIMIT 10
OFFSET 10

However, for larger databases, this is not a good solution.

To demonstrate, I created a database and filled it 2,000,000 tweets. Well, not real tweets but just rows of data.

The database is on my laptop and is only 500mb in size so don’t worry too much about the specific numbers in my results but what they represent.

First, I’m going to explain why using offset for pagination is not a good idea, and then present a few better ways of doing pagination.

Offset Pagination

Here’s a graph showing how much time it takes to get each page. Notice that as the page number grows, the time needed to get that page increases linearly as well.

Graph of query time increasing linearly

Results:
200,000 rows returned
~17.7s
~11,300 rows per second
** unable to paginate all 2 million rows under 5 minutes

This is because the way offsets works are by counting how many rows it should skip then giving your result. In other words, to get the results from rows 1,000 to 1,100 it needs to scan through the first 1,000 and then throw them away. Doesn’t that seem a bit wasteful?

And that’s not the only reason why offset is bad. What if in the middle of paging, another row is added or removed? Because the offset counts the rows manually for each page, it could under count because of the deleted row or over count because of a new row. Querying through offset will result in duplicate or missing results if your data is ever-changing.

There are better ways to paginate though! Here’s one

Order Based Pagination

You can page on pretty much anything you can order your data by.

For example, If you have an increasing id you can use it as a cursor to keep track of what page you were on. First, you get your results, then use the id of the last result to find the next page.

SELECT * FROM tweet
WHERE id <= $last_id
ORDER BY id DESC
LIMIT 100

2,000,000 rows returned
~4.2s
~476,000 rows per second

This method is not only much faster but is also resilient to changing data! It doesn’t matter if you deleted a row or added a new one since the pagination starts right where it left off.

Here’s another graph showing the time it takes to page through 2 million rows of data, 100 rows at a time. Notice that it stays consistent!

Graph of query time staying constant

The trade-off is that it cannot go to an arbitrary page, because we need the id to find the page. This is is a great trade-off for infinite scrolling websites like Reddit and Twitter.

Time Pagination

Here’s a more practical example of pagination based on the created_at field.

It has the same advantages and one drawback as ID pagination. However, you will need to add an index for (created_at, id) for optimal performance. I included id as well to break the tie on tweets created at the same time.

CREATE INDEX on tweet (created_at, id)

SELECT * from tweet
WHERE (created_at, id) <= ($prev_create_at, $prev_id)
ORDER BY created_at DESC, id DESC
LIMIT 100

2,000,000 rows returned
~4.70s
~425,000 rows per second

Conclusion

Should you really stop using OFFSET in your SQL queries? Probably.

Practically, however, you could be completely fine with slightly slower results since users won’t be blazing through your pages. It all depends on your system and what trade-offs you’re willing to make to get the job done.

I think offset has its place but not as a tool for pagination. It is much slower and less memory efficient than its easy alternatives. Thanks for reading!

✌️

1 Weird CSS Property You Should Use In Your Next Web Project

2020-02-21 12:22:27

First, we need to familiarize you with the CSS Box Model.

Skip to the quiz in the next section if you already know the box model

CSS Box Model

content-box-model

The box model describes the basic structure of one html element. Note that this is not to scale, so the pixels do not match up in the image!

  • The blue dotted box is the width x height of the content in the html element
  • Purple space is padding in the html element that surrounds the content with empty space
  • Border is the line around the html element. Used to set a border around the element.
  • Margin is like padding (empty space) but goes outside the border — determines space between elements.

Understand? Good you’re ready for the quiz

Quick CSS Quiz

Imagine that a you have a div with a width of 50px, height of 5px, a 1px border, and 10px of padding all around it.

What is the total width of this div, including the border and padding?

Answer: 72px

To get that answer you had to start with the width of 50px add the left-side and right-side of the border for 2px and lastly add the left-side and right-side padding for 20px.

content-box-model-edited

But it doesn’t have to be this way!

Why did we need to make all these calculations to measure a simple width of a div? Why can’t the css width actually refer to the width of the div?

Well there is another box-model we can use!

Introducing: box-sizing css property

First, I’ll go over the default value for box-sizing, which is content-box. Then I’ll go over the alternative setting, border-box!

In short, with content-box the width and height properties in css refer to the width and height of the html content, not including padding and border. With border-box the width and height properties refer to the total width and height of the html, which includes the padding and border.

content-box (default)

If box-sizing: content-box property is set, the width and height property only applies to the content section. The resulting width of the entire html element is 72px, including the extra 20px from padding and 2px from the border. And the resulting height is 5px.

content-box-css-model

box-model-content-box

✨border-box ✨

If box-sizing: border-box property is set, the width and height property is applied to the entire element. The content section is calculated after including the padding and border so the width of the entire child element is 50px with a content width 28px, padding of 20px and 2px of border. The height of the content is now calculated to be 0 because the 10px padding on top and bottom satisfy the 5px height requirement.

border-box-css-model

box-model-border-box

Playable CodePen

I made demo in CodePen! Feel free to open up the inspector and see the changing width.

When the box-sizing property is set to content-box, the width is 72px.

When the box-sizing property is set to border-box, the width is 50px!

https://codepen.io/abdisalan/full/NWqNYgJ

History

Of course you can’t talk about the history of the web without mentioning Internet Explorer.

The border-box model, where the width and height include the padding + border, was used and popularized by Internet Explorer. In contrast, the world wide web consortium (W3C) have decided that the content-box model is the standard and is currently used and the default for most modern browsers.

Which do you prefer?

I personally prefer border-box, but they each have their pros and cons. Tell me which one you prefer on twitter!

  • border-box — You don’t have to think about adding padding or border, they do the calculation for you and it’s supported by older browsers.
  • content-box — Default behavior in most browsers. Lets you dictate the size of the content so it might be easier to fit other elements within.

If you want to include border-box in your next project, you can use this handy CSS rule to apply it to all your elements.

* {
    box-sizing: border-box;
}

Hope you enjoyed this post and you start using border-box in all your projects!

✌️

How To Download A File using ReasonML

2020-02-13 11:44:23

The solution is to use javascript. For now.

Some of the methods we use here, such as Blob aren’t yet implemented in ReasonML (or I just couldn’t find them), so we need to put some javascript into our ReasonML code using interop.

Here’s a working version using codesandbox you can play with.

https://codesandbox.io/s/how-to-download-arbitrary-data-reasonml-yoxyg

Javascript Interop

In ReasonML, you can wrap javascript with [%bs.raw {| ...code goes here... |}] and it is accessible to the Reason code.

How Downloading works

First, we create a blob (Binary Large OBject) with our text MIME type.

var content = new Blob(["I AM TEXT"], {type: "text/plain"});

Then we create an object url that can reference the data in our Blob and we add this url to the anchor tag.

document.getElementById("download")
        .setAttribute("href", window.URL.createObjectURL(content));

Lastly, we set the download attribute so that we can name our downloaded file.

document.getElementById("download")
        .setAttribute("download", fileName);

In conclusion, the link to the blob causes the browser to download the file, and gives it our name.

All the code together:

index.html

<html>
<body>
    <a href="" id="download">Download File</a>
</body>
</html>

index.re

let setupDownload = [%bs.raw
    {|
    function() {
        var fileName = "data.txt";
        var content = new Blob(["I AM TEXT"], {type: "text/plain"});

        document.getElementById("download")
                .setAttribute("href", window.URL.createObjectURL(content));

        document.getElementById("download")
                .setAttribute("download", fileName);
    }
    |}
];

setupDownload();