rgrinberg has quit [Quit: My MacBook has gone to sleep. ZZZzzz…]
zebrag has quit [Read error: Connection reset by peer]
gravicappa has joined #ocaml
mro has joined #ocaml
mro has quit [Remote host closed the connection]
mro has joined #ocaml
gravicappa has quit [Ping timeout: 268 seconds]
epony has quit [Quit: QUIT]
kaph has joined #ocaml
random-jellyfish has joined #ocaml
olle has joined #ocaml
dextaa_5 has quit [Remote host closed the connection]
epony has joined #ocaml
dextaa_54 has joined #ocaml
CalimeroTeknik has quit [Changing host]
CalimeroTeknik has joined #ocaml
perrierjouet has quit [Ping timeout: 260 seconds]
<ns12>
Hello, if I find myself occasionally struggling with the type system, is there a book I can read to learn more about the type system?
<ns12>
I am thinking of reading "Types and Programming Languages" by Benjamin C. Pierce, but I don't know if that would help.
<d_bot>
<orbitz> What sort of issues do you run into?
<ns12>
I write programs that are not correctly typed ...
<d_bot>
<orbitz> I figured that part out
<ns12>
because I don't know the limitations of the type system.
<d_bot>
<orbitz> Is there a pattern in the issues you run into?
<ns12>
Hard to say ...
<ns12>
I guess I just want to learn the type system so that I can design APIs in my head without encountering type errors when I actually implement them.
<d_bot>
<orbitz> I don't know of a book or any resource other than just trying to implement things
<d_bot>
<orbitz> But getting errors from the type system isn't bad, not understanding how to resolve the errors is an issue
<ns12>
I use too much Lisp, and some Lisp-like ways of expressing things is impossible in OCaml.
<ns12>
"not understanding how to resolve the errors is an issue" - That is my issue, unfortunately.
<d_bot>
<orbitz> Practice practice practice
<ns12>
Often, when implementing library functions in OCaml, there is a non-type-safe way to do it and a type-safe way. But designing the type-safe implementation seems to require some understanding of the type system ...
<octachron>
"Types and Programming Languages" would be useful to learn how type system are designed, but it not necessarily a fast path in learning how to write correctly typed programs.
<ns12>
For example, https://github.com/anuragsoni/routes (a type-safe URL router). I actually have no idea how the API is implemented. I have tried for a few days now to make a clean room implementation.
<d_bot>
<orbitz> Something like that is probably using some more advanced concepts
<ns12>
octachron: Is that the slow way? I don't mind slow methods.
<d_bot>
<orbitz> I implemented a type safe router by studying other ones and then just building it up slowly and asking qusetiosn
<octachron>
That is a GADT-heavy library.
mro has quit [Remote host closed the connection]
<octachron>
That's the the slow way, but also one that doesn't preclude practice. Typically, GADTs are not-that-complicated from the type system perspective, but they require a copious amount of practice before become intuitive
<ns12>
"Something like that is probably using some more advanced concepts" - What are those "advanced concepts" and how do I learn them>
<ns12>
s/>/?/
<d_bot>
<orbitz> Yeah. I find GADTs really fascinating in that the actual change from an ADT is relatively minor, but the consequences are astounding
<d_bot>
<orbitz> ns12: for example, GADTs
<octachron>
And yes, GADTs do become intuitive at some point. To such way, that an important step in learning GADTs is to relearn "how to not use GADTs".
olle has quit [Quit: leaving]
olle has joined #ocaml
kaph has quit [Ping timeout: 246 seconds]
<ns12>
How do I learn that?
<d_bot>
<orbitz> Practice
<ns12>
Is practice alone enough to fully appreciate these kinds of things?
<ns12>
octachron: "That is a GADT-heavy library." - Thank you for the tip.
<d_bot>
<orbitz> Depends on how good of a practicer you are
<octachron>
Some canonical examples of GADTs: a shallow embedded interpreter for a small DSL, lists with statistically known lengths, heterogeneous lists
<octachron>
Not exactly, but it is still possible to have a readable format GADTs.
<octachron>
For instance "Hello %s" is essentially syntactic sugar for String_literal ("Hello ", String (No_padding, End_of_format))
mro has quit [Remote host closed the connection]
mro has joined #ocaml
<ns12>
So, it's not possible for me to implement `myprintf "Name: %s Age: %d" "Alice" 123` ?
<octachron>
Yes, but you can implement `myprintf [Lit "Name: "; String; Lit "Age: "; Int] "Alice" 123`
<ns12>
octachron: Is the Printf.printf function handled specially by OCaml?
<octachron>
and then use a ppx to get to `myprintf {%fmt|Name: %s Age: %d|} "Alice" 123`
<octachron>
The `Printf.printf` is an ordinary function. It is "Name: %s Age: %d" which is not a string because its type is _ format6
<octachron>
Ah, slight correction, you can reuse the Format GADTs to really immplement `myprintf "Name: %s Age: %d" "Alice" 123`
<octachron>
It is just that Format/Printf cover a large API which is a bit unwieldy to reuse in a learning project.
<octachron>
(And there is the issue of the "%{...}" specifier)
gravicappa has joined #ocaml
<ns12>
octachron: So, it's possible to create `myprintf [Lit "Name: "; String; Lit "Age: "; Int]` which has type `string -> int -> 'a` in OCaml by using GADTs? Is it possible to implement such a thing without GADTs?
<d_bot>
<orbitz> Depending on the API you can accomplish some similar things with closures
<d_bot>
<orbitz> The API would probably not look like that, though (lists)
<octachron>
Also note that this is not a stdlib's list but a heterogeneous list.
<octachron>
Also the type would not be "string -> int -> 'a" but "string -> int -> t" for some t.
<octachron>
But yes, it is otherwise possible with GADTs, or with some complex use of closures, but not with simple ADTs.
<ns12>
orbitz: "What sort of issues do you run into?" - One of them is how to define a function `myprintf` that when used like `myprintf [Lit "Name: "; String; Lit "Age: "; Int]` results in something with type `string -> int -> t`.
<ns12>
I guess GADT is the answer, which I have not learned about before.
<d_bot>
<orbitz> ns12: IME, writing such functions is fairly uncommon.
<ns12>
octachron: "But yes, it is otherwise possible with GADTs, or with some complex use of closures ... " - Really? It's possible with closures?
random-jellyfish has quit [Quit: Client closed]
<ns12>
Hmm ... I guess something like this could work:
<ns12>
let lit s = fun k -> k
<ns12>
let (/+) a b = fun k -> a (b k)
<ns12>
let int = fun k (i : int) -> k
<ns12>
let str = fun k (s : string) -> k
<ns12>
Then, `lit "Name: " /+ str /+ lit "Age: " /+ int` will have type `t -> string -> int -> t`.
mro has quit [Remote host closed the connection]
mro has joined #ocaml
mro has quit [Remote host closed the connection]
mro has joined #ocaml
perrierjouet has joined #ocaml
Haudegen has joined #ocaml
bartholin has quit [Ping timeout: 248 seconds]
bartholin has joined #ocaml
dextaa_54 has quit [Read error: Connection reset by peer]
dextaa_54 has joined #ocaml
kaph has joined #ocaml
mro has quit [Ping timeout: 246 seconds]
hannes__ is now known as hannes
bartholin has quit [Ping timeout: 246 seconds]
bartholin has joined #ocaml
mro has joined #ocaml
perrierjouet has quit [Ping timeout: 272 seconds]
mro has quit [Remote host closed the connection]
mro has joined #ocaml
mro has quit [Ping timeout: 260 seconds]
perrierjouet has joined #ocaml
kaph has quit [Ping timeout: 246 seconds]
mro has joined #ocaml
kaph has joined #ocaml
Anarchos has joined #ocaml
bartholin has quit [Ping timeout: 240 seconds]
mro has quit [Remote host closed the connection]
bartholin has joined #ocaml
mro has joined #ocaml
mro has quit [Remote host closed the connection]
mro has joined #ocaml
bartholin has quit [Ping timeout: 272 seconds]
rgrinberg has joined #ocaml
bronsen has joined #ocaml
bartholin has joined #ocaml
<d_bot>
<ec> Is there a codemod library, something like `jscodeshift`, to simplify and speed up writing small codemods for OCaml source-code?
<d_bot>
<ec> in particular, the fact it depends on a parser/lexer that intentionally *do not* throw away information, in the hopes of reconstituting the original code with the least-possible changes, seems like something I've not come across in the ML ecosystems …
spip has joined #ocaml
bobo has quit [Ping timeout: 248 seconds]
dextaa_54 has quit [Read error: Connection reset by peer]
dextaa_54 has joined #ocaml
<d_bot>
<VPhantom> I never dealt with "codemods" (had to look up the word just now) but in my experience with OCaml the type system is all I have needed when refactoring. I haven't maintained large code bases yet though.
<companion_cube>
oh hey @VPhantom
<d_bot>
<VPhantom> Bonjour! 😃
<d_bot>
<bikachuu> Hello
neilthereildeil has joined #ocaml
<neilthereildeil>
hey guys
<neilthereildeil>
what does this code do?
<neilthereildeil>
List.iter (add_event (fun x -> x.read <- true)) in_fds;
<neilthereildeil>
can someone please break it down into peices?
<neilthereildeil>
i think this is an anonymous function: fun x -> x.read <- true
<d_bot>
<bikachuu> x is a record that has a mutable field `read : bool`, and this anonymous function sets that field to `true`
zebrag has joined #ocaml
<d_bot>
<VPhantom> I guess `add_event` takes more than one argument and is curried here.
<d_bot>
<ec> A big part of the point is to have the changes stored, historically — it's also really helpful to have a branch be not just a set of changes and a *human* ability to rebase those changes on top of others' work when they're ready … but to also have the ability to *mechanically* apply those changes to any rebasing branches.
<d_bot>
<VPhantom> Hence why it's higher-level than a mere commit. I see.
<d_bot>
<ec> idk if i explained that well, not very awake. tl;dr "human following type-system" + "lots of contributors" == "that human having to make those changes all over again every couple of days/weeks";
<d_bot>
<ec> but "machine making changes" + "lots of contributors" == "machine re-making those changes for each contributor"
<d_bot>
<ec> it also has a couple nice side-effects w.r.t. debugging down the road, and w.r.t. bikeshedding — it's a bit like an autoformatter in that respect.
<neilthereildeil>
so do we evaluate the code from the most nested expression, outwards?
<neilthereildeil>
in this case, do we read the "fun x -> x.read <- true" first?
<d_bot>
<ec> it's eager evaluation, so (mostly) yes
<neilthereildeil>
so frm the documentation, it looks like it executes that function on each element of the list, right?
<d_bot>
<ec> if you're unfamiliar with currying, it's important to note that there's effectively a second anonymous function in there as well
<d_bot>
<ec> List.iter (fun y -> add_event (fun x -> x.read <- true) y) in_fds
<d_bot>
<ec> this is because (I'm assuming, from looking at the code) `add_event` must be partially applied — it needs more arguments than just that first `fun x ...` lambda.
<d_bot>
<VPhantom> Exactly.
<d_bot>
<Ambika E.> I'm trying to organize a dune library in a way such that I have a top-level module, call it `Node`, with some top-level values, e.g. `Node.create`, but I want it to have sub-modules as well, e.g. `Node.Server` and I want those modules to exist in their own files. Is this possible? I know I can do this in one file quite easily, but I'm not sure how splitting it into several works if i want those top-level values
<d_bot>
<ec> Can explain in more detail if the docs don't cover it, feel free to ask! (=
<d_bot>
<octachron> Yes, if you have a library called `lib` and a module `Lib`, `dune` will consider that the module `Lib` is the main entry point of the library. Then, you just have to re-export the other modules in the library with `Module Sub = Sub`.
<neilthereildeil>
i just read about currying
<neilthereildeil>
so it seems the function format myFunc a b c is "curryed"
<d_bot>
<octachron> @ec : `(wrapped false)` is not that much of a good advice because it exposes the non-qualified names `Sub` and thus it increases the risk of name collisions.
<neilthereildeil>
are you only thinking its curried because its partially applied?
<d_bot>
<ec> Oh, I didn't know about the matching-name-single-export functionality
<d_bot>
<ec> I thought you'd just end up with `Lib.Lib.create`, which is what he's trying to avoid. Cool1
<d_bot>
<Ambika E.> wrapped false is not exactly what i want
<d_bot>
<Ambika E.> that would expose _every_ module in my library directly
<neilthereildeil>
also how do you knoiw the function is partially applied?
mro has quit [Remote host closed the connection]
<d_bot>
<Ambika E.> it's partially applied because the second argument to `List.iter` is a function, meaning that the application of `add_event` returned a function rather than a non-function value. This implies partial application to some extent.
<d_bot>
<ec> `List.iter` takes a function as its first argument; and based on the name `add_event`, I'm guessing that second clause doesn't return a function when it's fully-applied. Just a guess, though. ¯\_(ツ)_/¯
<d_bot>
<Ambika E.> first argument, my bad
<d_bot>
<Ambika E.> the structure i want to mimic with dune is something like
<d_bot>
<Ambika E.>
<d_bot>
<Ambika E.> node.ml
<d_bot>
<Ambika E.> ```ocaml
<d_bot>
<Ambika E.> let create ... = ...
<d_bot>
<Ambika E.>
<d_bot>
<Ambika E.> module Server = struct
<d_bot>
<Ambika E.> let init ... = ...
<d_bot>
<Ambika E.> ...
<d_bot>
<Ambika E.> end
<d_bot>
<Ambika E.> ```
<d_bot>
<Ambika E.>
<d_bot>
<Ambika E.> such that the consumer of this library could run `Node.create` or `Node.Server.init`.
<neilthereildeil>
heres the signature of iterator:
<neilthereildeil>
val iter : ('a -> unit) -> 'a list -> unit
<d_bot>
<Ambika E.> `wrapped false` just means that the consumer can directly run `Node.create` or `Server.init`
<d_bot>
<Ambika E.> not necessarily what i'm looking for
<neilthereildeil>
the () means that the first argument is a function, right?
<d_bot>
<Ambika E.> yeah, that's what it's signifying
<neilthereildeil>
in
<neilthereildeil>
List.iter (add_event (fun x -> x.read <- true)) in_fds;
<neilthereildeil>
it looks like in_fds is the second argument
<neilthereildeil>
right?
<d_bot>
<Ambika E.> yes
<neilthereildeil>
so then how is it partially applied?
<neilthereildeil>
i see it as List.iter () in_fds
<neilthereildeil>
where the () is the function
<d_bot>
<ec> you'll have to ask @octachron about it — but it sounds like that's only possible if the *library* is also named `node`? hrm.
<d_bot>
<Ambika E.> even if it were, i don't think my problem is solved, it doesn't seem like you can expose top-level values with dune libraries that way
<d_bot>
<Ambika E.> i basically want something like python's `__init__.py`
<d_bot>
<octachron> You can add any toplevel values that you want in `node.ml`.
<d_bot>
<Ambika E.> really?
<neilthereildeil>
also, what does "<- true" do?
<d_bot>
<Ambika E.> that's assignment to a mutable record field
<d_bot>
<octachron> Yes, the only constraint is that you also need to export the submodules of the library
<d_bot>
<ec> `<-` is OCaml's weird-ass syntax for "mutating assignment." Depending on the language you're coming from, it's probably best read something like `=`. :P
<d_bot>
<Ambika E.> i see, i'll give this a try, i appreciate it a lot
<d_bot>
<Ambika E.> yeah, see, add_event takes 2 arguments, not one
<neilthereildeil>
add_event looks like it returns e
rgrinberg has quit [Quit: My MacBook has gone to sleep. ZZZzzz…]
<d_bot>
<Ambika E.> the fact that you only pass it one argument means it's being applied partially
<neilthereildeil>
wow thats confusing
<d_bot>
<Ambika E.> not once you get used to it
<d_bot>
<Ambika E.> it's a common pattern
<neilthereildeil>
ok so add_event is partially applied
<neilthereildeil>
because we dont pass enough parameters?
<d_bot>
<ec> Partial application is super-important. If you're coming from a non-functional background, it's best to think of it as "every function *defaults* to returning another function, if you don't give it everything it needs."
<d_bot>
<Ambika E.> yeah, you pass the first parameter, so the result is a function that's still waiting for the second param
<neilthereildeil>
lol wow so its liike partially executed??
<d_bot>
<ec> i.e. `let mult a b = a * b`, if you only give it `a`, becomes `let mult a = (fun b -> a * b)` automatically.
<d_bot>
<Ambika E.> the function won't actually get called until it gets all its inputs
<d_bot>
<Ambika E.> it kind of creates a half-filled shell of a function call
<neilthereildeil>
wow cool
<d_bot>
<Ambika E.> it is really cool
<d_bot>
<ec> it's very helpful for all sorts of things.
<d_bot>
<ec> 1. you can explicitly use that to create specialized, helper functions: `let triple = mult 3`
<neilthereildeil>
so then when is add_event actually executed?
<neilthereildeil>
when does it have enough params?
<d_bot>
<Ambika E.> in the execution of List.iter
<d_bot>
<ec> 2. And in situations like the above, you don't have to waste a bunch of space typing out a lambda; you can just pass it an unfinished function, as long as the "last" argument to the function is the operable item
<d_bot>
<Ambika E.> List.iter plugs each file descriptor in `in_fds` into the partially applied function
<neilthereildeil>
so is in_fds an argument to List.iter or add_event?
<d_bot>
<Ambika E.> `in_fds` is a list of arguments to `add_event`, and thus it is an argument to `List.iter` 😎
<d_bot>
<Ambika E.> that's how list iter works
<d_bot>
<Ambika E.> it takes a function and a list of things you want to apply the function to
<d_bot>
<Ambika E.> and it does just that
<d_bot>
<Ambika E.> it's like a specialized for loop condensed into one line
<neilthereildeil>
damn
<neilthereildeil>
very dense
<neilthereildeil>
so is in_fds passed to List.iter first, and List.iter passes it as a parameter to add_event interally?
<d_bot>
<Ambika E.> let me show you a simpler example of how List.iter is used
<d_bot>
<Ambika E.> there's no partial application here, but you can kind of see that the argument to `List.iter` is obviously not the argument to `multiply`
<d_bot>
<ec> I also added a JavaScript equivalent (if you let me know what language you're most familiar with, neil, I can possibly use that instead?)
mbuf has quit [Quit: Leaving]
<neilthereildeil>
ec: ok, it took like 8 minutes but i understand ur code
<d_bot>
<ec> ❤️
<neilthereildeil>
thax so much. im a C guy, so i think in a totally different
<neilthereildeil>
way
<d_bot>
<ec> ahhahhah, oof, that's a *big* transition. good luck!
<d_bot>
<ec> don't be afraid of grabbing any code you are confused by, dropping it into `utop` or a text-editor with LSP support, and starting to add a bunch of explicit anonymous-functions everywhere. if you screw something up, the type-system will tell you. it's a good stretch if you're not familiar with partial-application and such.
<neilthereildeil>
Ambika: thanks for the code
<d_bot>
<ec> I'm surprised this Discord doesn't have a #menhir or #parsing channel for Menhir / Sedlex stuff
<neilthereildeil>
ec: im just trynna fix 1 bug, and im (re)learning ocaml for this! haha
<neilthereildeil>
also, that anonymous function:
<neilthereildeil>
fun x -> x.read <- true
<neilthereildeil>
unconditionally sets "read" field to true, correct? what if no read field exists in the object?
<d_bot>
<ec> impossible, all constrained by the type-system. `x` has a very specific type. I really hope you're reading this inside an editor with `ocaml-lsp` or similar, by the way — I'm a pretty deft hand with OCaml, and I'd be *totally* incompetent without a 'what's the type of this value' hotkey :P
<octachron>
Records are nominal and statistically typed. The field is guaranteed to exist in the record.
<d_bot>
<ec> even just for fixing one bug, it's worth quickly installing whatever editor-plugin is relevant.
<neilthereildeil>
the type of x is not explicitly listed in the code
<d_bot>
<ec> yep, we very very rarely type out types manually — mostly only as a form of comments/documentation, honestly. OCaml is really heavy on the type-inferrence.
<d_bot>
<ec> Was just about to suggest we move this to #beginners, but ah. you're over the IRC bridge.
rgrinberg has quit [Quit: My MacBook has gone to sleep. ZZZzzz…]
<neilthereildeil>
also, one thing i didnt understand in the compact form:
<neilthereildeil>
List.iter (add_event (fun x -> x.read <- true)) in_fds;
<neilthereildeil>
is in_fds first passed to iter, which then passes it to add_event?
<d_bot>
<ec> half-yes — `in_fds` is indeed an argument to `iter`, but only *one* `in_fd` is passed to `add_event`.
<d_bot>
<ec> that being the entire purpose of `iter`
<neilthereildeil>
ok cool. i just wanted to make sure i understood the order of parameter passing
<neilthereildeil>
also, since fun unconditionally sets x.read=true, where are we guaranteed that x has a read field?
<d_bot>
<ec> by that code existing, basically.
<d_bot>
<NULL> compiling*
<d_bot>
<ec> the compiler will infer from that code _that_ `x` is a value with a record-type that looks like `{ mut read: bool; … }`, and then it'll explode if the values in `in_fds` don't match that expectation.
<neilthereildeil>
okay compile time. thats better than python where the server could be running for 2 days and then throws an error that an attribute doesnt exist!
<d_bot>
<NULL> OCaml is pretty much as statically typed as you get
Anarchos has quit [Quit: Vision[]: i've been blurred!]
rgrinberg has joined #ocaml
szkl has joined #ocaml
<olle>
Hmmm
<olle>
No refined typed tho :))
<olle>
Or dependent, etc etc
<olle>
Or even kinds?
<d_bot>
<NULL> I didn't say it has the most types
<d_bot>
<NULL> had*
<d_bot>
<ec> i may be being crotchety, but … in real-world development, most of OCaml's *existing* complicated type-features don't really get used much. Honestly, it's often "best avoided".
<d_bot>
<ec> Not saying gaining those features would be a net loss, but I *am* saying that not having them isn't a significant drawback, at least for pragmatic production codebases. :P
<d_bot>
<ec> "Good code can be understood by an intern; better code is that-but-less-of-it; and the best code is none at all." and all that
<d_bot>
<octachron> I don't completely agree, GADTs are not completely trivial, and tend to spread to code base pretty fast.
dextaa_54 has quit [Read error: Connection reset by peer]
dextaa_54 has joined #ocaml
<d_bot>
<Ambika E.> any idea what's going on here?
<neilthereildeil>
that signature definition language is just as confusing as the code itself, so i looked elsewhere! LOL
* d_bot
<ec> laughs
<d_bot>
<ec> I can relate, I've struggled with Haskell's syntax for *years*. Unfortunately it's used absolutely everywhere in fp and strong-typing spaces.
<d_bot>
<ec> The important part in the definition is the `'blah` type-variables, with a single-quote before them. Any two sharing a letter are the same type.
<d_bot>
<ec> I personally cannot fathom why so many in the FP space (including other OCamleers) struggle to type more than a single letter for typevar names. If it helps:
<neilthereildeil>
the function signature i think...
<companion_cube>
same thing
<companion_cube>
well, everything on the left of `-> ` is a function argument
<companion_cube>
here: ('elt -> 'acc -> 'acc) is an argument
<companion_cube>
'elt array too
<companion_cube>
and 'acc too
neilthereildeil has quit [Quit: Client closed]
<companion_cube>
ahah fuck
neilthereildeil has joined #ocaml
<neilthereildeil>
so ('elt -> 'acc -> 'acc) is the argument to fold_right?
<companion_cube>
it's one of the arguments
<companion_cube>
and it's itself a function
<neilthereildeil>
im confused
<neilthereildeil>
i thought all the arguments are inside the ()
<companion_cube>
no :)
<neilthereildeil>
so what does () mean here?
<companion_cube>
it's used to delimit a type
<neilthereildeil>
like a tuple?
<neilthereildeil>
since theres 3?
<companion_cube>
like in `a * (b + c)` it is used to delimit a sub-expression
<companion_cube>
here it's used to delimit a type
<neilthereildeil>
whats the type thats delimited here?
<companion_cube>
'elt -> 'acc -> 'acc
<neilthereildeil>
looks like 3 types to me...
<companion_cube>
I think you need to read about simpler types than that first
<companion_cube>
no, it's a function type because of the ->
<companion_cube>
`Array.set : 'a array -> int -> 'a -> unit` for example
<companion_cube>
that's the function to set an array slot by its index
<neilthereildeil>
ok, so if i have -> between types then its a function?
<d_bot>
<NULL> The way you read these types is related to the currying we talked about above
<d_bot>
<NULL> Here, `Array.set` takes 3 arguments: one array, an int, a value (with type 'a, the types of values on the array) and returns unit
<d_bot>
<NULL> You can also see it as a function that takes an array and returns a function that then takes an int and returns a function that then takes a value and returns unit