Skip to content

[PR-1190] Article about typescript for haskellers #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Apr 2, 2022

Conversation

sashasashasasha151
Copy link
Member

No description provided.

@NaeosPsy NaeosPsy requested a review from serokellcao November 30, 2021 15:21
Copy link
Member

@Martoon-00 Martoon-00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed about 70% of the text, so far so good, really interesting info.

Left some comments for now.

Also, I think at some point later we will need more reviewers on the technical part because I find some points in the storytelling/examples/explanations controversial (aside from those that are mentioned in the review).

let a: A = {};
const b: B = {x: 1};
a = b;
console.log(a.x); // Property 'x' does not exist on type 'A'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Property 'x' does not exist on type 'A'. the outputted text or compile-time error? If it is the latter, I would simplify to const c = a.x to make it clearer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:cho:

@NaeosPsy NaeosPsy requested a review from Heimdell December 6, 2021 21:07
Copy link
Contributor

@Heimdell Heimdell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide explanation (hand-wavy at least!) on how all of that black magic works.

Without it, this will scare the haskellers.

It doesn't matter how small or complex it is.
From one click apps with pictures converting to photoshop everyone wants fast and easy access to it and Web is where you can give it to your users or even for you with the easiest way.

Moreover, UI and UX standards are increasing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raising or improving

From one click apps with pictures converting to photoshop everyone wants fast and easy access to it and Web is where you can give it to your users or even for you with the easiest way.

Moreover, UI and UX standards are increasing.
People like simple interfaces with enjoyable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with joy

Application with worse functionality and better UI/UX may win on the market against the other one with bad design.

One might say that doesn't matter what your application is about or what part of it you write.
In the end in most of the cases you will need to give access for it to smb.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In the end in most of the cases you will need to give access for it to smb.
In the end you'll have to give access for it to somebody other.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest replacing dot with comma and gluing the next sentence.


type ToS<A> = {
toString: string;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't ToS be Interpreter<String>?

Copy link
Member Author

@sashasashasasha151 sashasashasasha151 Dec 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, the idea was to take example from our fp course https://slides.com/fp-ctd/lecture-11#/18/0/1

And as I understand the problem (or it is not a problem) here is that it is an imitation of HKT. Also we can add to URItoKind anything we want. Also we use just type aliases. So, yes I can write just Interpreter<String> but I wanted to make it look similar to Haskell.

Also I don't really know what different interpreter to write as we don't use <A> in ToS (but we also don't use it in Haskell implementation)

So, if you have any ideas I will change it.

}
}

const arithInterpreter: ArithExpr<"Interpreter"> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArithExpr<"Interpreter">

What. How does it work? Please explain why are we putting a name of a field here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, URI is an interface to string-like objects, but what is it here, exactly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I thought that HKT part explain it (do I need to explain in here again?)
URIS is just keys(properties) of URItoKind where URItoKind is like map from keys to types
Here we say that ArithExpr take Expr extends URIS and types in ArithExpr will be Kind<Expr, number> for example where Kind is a type from URItoKind with concrete keys(properties) from URIS
:ghc:
Let's think how to make it more understandable if HTK part doesn't describe it


testExpr(arithInterpreter).interpret; // false
testExpr(arithToS).toString; // (((23 + 12) > 170) && (35 > 47))
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above looks deceptively similar to haskell implementation, but in reality, there's a lot of magic in need of explanation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the blocks of code, I usually consider them subjectively understandable, if I can sit and make an analogous code myself, given the example and an explanation.

Here, I can't, because there is no textual explanation.

and: (l: Kind<Expr, boolean>, r: Kind<Expr, boolean>) => Kind<Expr, boolean>;
};

type Interpreter<A> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading the code block to the end, I start wondering what the A here really is. Plz explain how it is connected to other spells.

This is the very simple examples of working with ADT like structures in TypeScript.
Using type parameters you are also able to make some more complex things like [such](https://github.com/pfgray/ts-adt) library for ADTs.
But one of the most useful features of Haskell connected with ADTs is pattern matching.
In allows you to disclose you structure comparing it with some pattern and based on this condition execute some code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not able to understand this sentence, sorry. :D

@sashasashasasha151
Copy link
Member Author

Looks like I added more code descriptions and answered/fixed all comments. It is possible that I missed smth or some parts need more detailed explanations

@sashasashasasha151 sashasashasasha151 marked this pull request as ready for review December 27, 2021 04:00
Copy link
Member

@Martoon-00 Martoon-00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the remaining part (starting from the mapped types) ~superficially, looks quite good overall.

Things look really well explained (but not all of them yet, hope that resolving the remaining @Heimdell's comments will improve the situation). I mostly had no questions like "WTF is going on here". Many examples looked sort of familiar, so it was relatively easy to understand them (though will I be able to re-implement something here with the same ease - that would be an interesting question).

But for future reference, I again express my concern regarding the number of covered topics. The article starts from the basic things like syntax and types, then without giving a break it introduces tricky types where some decent understanding of basics is already required. Then the post picks up the pace even more, using concepts that are not explained anywhere, and keeping this pace is now hard for you as a writer.

We will use `fp-ts` to implement it.

```typescript
import { Kind, URIS } from "fp-ts/HKT";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scope of the post is really large, the difficulty of described concepts grows exponentially throughout the text, so many such issues arise.

But need to say, things that are now explained (like Kind) looked explained quite well to me and embedded into the text nicely. At least those that are similar to what I have already worked with in Haskell. Other things still look mind-blowing, but that's probably exactly the scope of the post to only provide the transition from known Haskell concepts to TS.

@NaeosPsy
Copy link
Contributor

One suggestion: I think it would be super beneficial if you provide a table that enlists the Haskell syntax and the corresponding TS syntax.

https://haskellfortypescriptdevs.fission.codes/part-i -- Just saw this, perhaps it can serve as partial inspiration.

@sashasashasasha151
Copy link
Member Author

@Martoon-00

One suggestion: I think it would be super beneficial if you provide a table that enlists the Haskell syntax and the corresponding TS syntax.

You mean in the end of the article (to summary part) or how? First of all when I started to write this article I thought that it will be for people who already knew the TypeScrips syntax. Then in review there was a good point to describe some non-trivial things and I added them.

But, hm, I don't really know what to add in this table if it will be in the beginning as the whole article is about translating Haskell code to TS code. I can do it in code parts like creating Haskell examples to all TS examples (but not really know how do it)

Anyway I am not against your suggestion, just want to understand how to make it more useful and not just copy paste of the article

But for future reference, I again express my concern regarding the number of covered topics.

I also thought about it. But in my head it looked like the first part is for all, there are no really hard concepts and the second part is for inspiration. I specially wrote that it is just examples with descriptions and for more info you need to go here and here. There is no reason to write a full article about them as only few amount of people will need it and even fewer amount of people will use it.
So, the idea (as I introduced it to myself) was to write some kind of dictionary where you can quickly find information but not a fully explanation. For example when you google about parser combinators in Haskell you will not find good explanation, but with last Heitor blog you can really understand all that you need. And when you will google TS Mapped types you will find ton of articles about them.
Not sure that you expected such answer. Maybe @NaeosPsy will add smth.

@Martoon-00
Copy link
Member

You mean in the end of the article (to summary part) or how?

Yes, I would put it at the end, or maybe even as a separate gist. This table should answer the question: "I want to declare X thing (e.g. polymorphic datatype) in TypeScript, remind me in 5 seconds which code should I write".

On the other hand, the resource that @NaeosPsy mentioned looks quite good, maybe leaving a link in some Useful resources section at the end of the post would be exactly sufficient.

So, the idea (as I introduced it to myself) was to write some kind of dictionary where you can quickly find information but not a fully explanation.

I see, told this way it really makes sense, yeah.

@NaeosPsy
Copy link
Contributor

https://serokell.io/blog/parser-combinators-in-haskell

You can also take a look at how Heitor handled it in appendix of parser combinator article.

@sashasashasasha151
Copy link
Member Author

@Martoon-00 @NaeosPsy I spent full day trying to understand how to make the table useful and informative
So, the first question is about the number of content in it. Do I need to write only about topics in the article in it, or maybe some more info.
In article there are some TypeScript specific things, so at first I tried to write TypeScript to Haskell table but in this case it is a little bit strange as this article is for Haskellers. On the other hand if I will write Haskell to TypeScript I will not cover some things.
In case of TypeScript to Haskell there also is an advantage that we can add some syntax specific things to it \

And after all table has really small amount of space to write and there is no ability to add code blocks
So, I propose to not create a table but maybe small part with just brief comparisons. In this case I will be able to add code blocks, add some TS specific info (and maybe TS specific syntax info)
What do you think about it?

@NaeosPsy
Copy link
Contributor

NaeosPsy commented Jan 31, 2022

@sashasashasasha151

You can definitely use tables + code blocks. (See Vlad's translations from Haskell to Core, we just did HTML tables: https://serokell.io/blog/haskell-to-core)

A TypeScript to Haskell guide would be quite odd in this case. This particular item should serve as an aid for memory, so it also doesn't have to contain explanations of TypeScript stuff.

Basically, you can look at it as creating a "cheatsheet" for your article. In case something is too long, you can just use an anchor link to the corresponding section.

You can also link to the other one at the bottom of it if you wish.

Edit: I've looked at the one you've already created, and it is okay, just that there is no space for the code. If you had only two columns at max, it would be solved. I think the table can be restructured or divided so that it works like that.

@sashasashasasha151
Copy link
Member Author

@Martoon-00 @NaeosPsy I made a table, not sure if it will look good on the site. Also I don't know if it contain information you thought about.

@Martoon-00
Copy link
Member

Martoon-00 commented Feb 3, 2022

The tables are just awesome! Thanks for adding them 👍


One more thing I'd like to see there: does TS have something like TypeError? So that I could write this

type G<A> = A extends number ? boolean : A extends string ? boolean : blowUp;

similar to

type family G a where
  G Int = Bool
  B String = Bool
  -- no "otherwise" clause

?

@sashasashasasha151
Copy link
Member Author

@Martoon-00
type G<A> = A extends number ? boolean : A extends string ? boolean : never;?

@serokellcao
Copy link
Member

serokellcao commented Feb 11, 2022 via email

@Martoon-00
Copy link
Member

@Martoon-00 type G<A> = A extends number ? boolean : A extends string ? boolean : never;?

Ah no, this would correspond to _ -> Void, which maps to an uninhabited but valid type, and I want to forbid all other cases for G so that G Float is a type error.

@sashasashasasha151
Copy link
Member Author

type G<A extends number | string> = A extends number ? boolean : boolean;

@Martoon-00
Copy link
Member

type G<A extends number | string> = A extends number ? boolean : boolean;

Ooh right, and this perhaps looks even better than for Haskell.

Could you include this? Like, here the correspondence to Haskell is not 1-to-1, would be nice to have this mentioned as a hint.

Copy link
Member

@Martoon-00 Martoon-00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing my comments!

Copy link
Contributor

@Heimdell Heimdell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks overall good.

Some demon-conjuration-spells are not clearly explained, but most of them are. I think readers should start poking into TS just out of initial contact shock.

The appendix with haskell-TS correspondence is nice.


In the first `if`, we pass `typeof result === "number"`, so we know that it may be only `function` or `string` in the code below.
And we can run `length.toString()` on this value (even if `number` doesn't have such a property) since both `function` and `string` have the property `length`.
But we can't `call` this value because `string` is not callable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But can we call it, if all constituents of the union are callable? Will it infer the result to be union of function result types?

Copy link
Member Author

@sashasashasasha151 sashasashasasha151 Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added note


```typescript
const resultInterpreter = (result: Result): string | undefined => {
if (typeof result === "number") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if statement is enriched with case analysis, when is used with typeof result === "number".

Will typeof result === "number" || typeof result === "string" do the trick, leaving type to be function in else-block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added note

return match(result)
.with({ type: 'error' }, (res) => `<p>Oups! An error occured</p>`)
.with({ type: 'ok', data: { type: 'text' } }, (res) => `<p>${res.data.content}</p>`)
.with({ type: 'ok', data: { type: 'img', src: select() } }, (src) => `<img src=${src} />`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the rule "res is the matched payload or arguments are the ones chosen with select()"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like yes, but ts-pattern have a lot of things, it is a simple example just for design understanding

a.x; // 2
```

For this reason, you should always write proper types of arguments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was moved here by mistake

});
```

In the code example above, `produce` is a base primitive for working with `immer`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it remove all readonly-limitations from type of a via Writable<>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know how it works under the hood, but what for it may need Writable? Search in their repo by Writable said that there is no Writable


#### Parametric polymorphism

Parametric polymorphism allows us to write abstract functions or data types that don't depend on their type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Parametric polymorphism allows us to write abstract functions or data types that don't depend on their type.
Parametric polymorphism allows us to write abstract functions that, for instance, don't depend on concrete types of particular arguments, and types that can abstract out parts of itself as type parameters.

import { identity } from "fp-ts/lib/function";

interface Equality<A, B> {
(a: A): B;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean, that Equality should be a funciton A -> B?

}
}

const arithInterpreter: ArithExpr<"Interpreter"> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, URI is an interface to string-like objects, but what is it here, exactly?

and: (l: ToS<boolean>, r: ToS<boolean>) => {
return { toString: `(${l.toString} && ${r.toString})` };
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My wild guess is that "ToS" here is a marker or has the same role as

instance IsExpr ToS where

in haskell.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand you right the answer is yes
I don't really like how it looks, but I wanted to make it looks like in haskell and didn't want to invent more complex examples as it is already complex enough. So, I thought a lot about it and as it is kind of advanced part I think we can leave it like this

@NaeosPsy NaeosPsy merged commit 7d97a1e into master Apr 2, 2022
@delete-merged-branch delete-merged-branch bot deleted the sashasashasasha151/pr1190-typescript-for-haskellers branch April 2, 2022 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants