Logs: liberachat/#haskell
| 2026-01-12 19:23:30 | <Enrico63> | > That said, while you can emulate the dynamic typing of pickle.load() if you really want to by deferring the type check until the last possible moment, the reality is that doing so is almost never actually useful. At some point, you have to make assumptions about the structure of the value in order to use it, and you know what those assumptions |
| 2026-01-12 19:23:30 | <Enrico63> | are because you wrote the code. **While there are extremely rare exceptions to this that require true dynamic code loading (such as, say, implementing a REPL for your programming language), they do not occur in day-to-day programming, and programmers in statically-typed languages are perfectly happy to supply their assumptions up front.** |
| 2026-01-12 19:23:31 | <Enrico63> | This paragraph is not representative of the whole post, which is about the idea that _static type systems are not fundamentally worse than dynamic type systems at processing data with an open or partially-known structure_; however, the highlighted portion of the paragraph I quoted just piques my curiosity about a different topic, which is "when is |
| 2026-01-12 19:23:31 | <lambdabot> | <hint>:1:10: error: parse error on input `,' |
| 2026-01-12 19:23:31 | <Enrico63> | a dynamic language (or a feature) really necessary?", and I'm curious as to why "implementing a REPL for your programming language" is such a case. |
| 2026-01-12 19:23:50 | × | ezzieyguywuf quits (~Unknown@user/ezzieyguywuf) (Read error: Connection reset by peer) |
| 2026-01-12 19:23:59 | <Enrico63> | Oh, forgot that the leading > has a meaning, sorry |
| 2026-01-12 19:24:33 | <Enrico63> | I known the question is not about Haskell, but I assume many people in here can help me understand :/ |
| 2026-01-12 19:28:17 | <dolio> | Implementing a REPL basically by-definition requires that. |
| 2026-01-12 19:28:29 | <tomsmeding> | Enrico63: if you implement a REPL, you genuinely have no assumptions about the values that the user might produce inside that REPL. So assuming that you represent those values somehow in the implementation of your REPL, you will be able to do no better than "it's a value" |
| 2026-01-12 19:28:38 | <dolio> | Because you type code into a REPL and it gets dynamically loaded. |
| 2026-01-12 19:29:11 | <tomsmeding> | I think this is what Alexis is getting at, in any case |
| 2026-01-12 19:30:00 | <tomsmeding> | more concretely, if you implement an interpreter for a language, then how do you represent the values computed in the language you're interpreting? |
| 2026-01-12 19:30:15 | <tomsmeding> | (I say "more concretely" because a REPL may well be compiled and not interpreted, like GHCi is) |
| 2026-01-12 19:30:39 | <tomsmeding> | (though the boundary between "compiled" and "interpreted" is a bit fuzzy here) |
| 2026-01-12 19:33:39 | <Enrico63> | Mmmmmmm. Say I have a language XYZ. And I tell that in XYZ you can write something like `eval("this is XYZ code")`. If this is possible, do you deduce that XYZ is necessarily a dynamically typed langauge? |
| 2026-01-12 19:33:59 | <EvanR> | if you're implementing a REPL for a language with a static type system, it just means you need to be dynamically adding to the static type definitions, so future inputs can be parsed and checked |
| 2026-01-12 19:34:17 | <EvanR> | the repl input is basically that eval (in some context) |
| 2026-01-12 19:34:37 | <EvanR> | doesn't require dynamic typing at all |
| 2026-01-12 19:34:42 | <EvanR> | see haskell |
| 2026-01-12 19:35:29 | <EvanR> | ghci |
| 2026-01-12 19:35:50 | <tomsmeding> | Enrico63: you can have a type with dynamic structure in a statically typed language: see aeson's Value |
| 2026-01-12 19:36:09 | <EvanR> | also that though it didn't seem to be the subject of the post |
| 2026-01-12 19:36:21 | <tomsmeding> | the "interpreter" example is an argument why you may not know what structure a particular type has, not that a statically-typed language is fundamentally unsuitable |
| 2026-01-12 19:37:09 | <tomsmeding> | in this case, note that aeson's Value is an explicit data type in Haskell that forces you to announce that an object comes now (Object), or a string comes now (String), etc. |
| 2026-01-12 19:37:32 | <tomsmeding> | whereas in a dynamically typed language, what was represented by that Value could simply be a nesting of objects and strings without intervening data constructors |
| 2026-01-12 19:37:36 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-01-12 19:37:37 | → | Lord_of_Life_ joins (~Lord@user/lord-of-life/x-2819915) |
| 2026-01-12 19:37:52 | <EvanR> | interpreter for a language always has to parse its input before doing anything. If it's statically typed it means you have more checks after the parsing, this isn't that much of a fundamental difference between languages |
| 2026-01-12 19:37:54 | <tomsmeding> | so for this very dynamic use case, we have to do a bit more work in a statically-typed language than we do in a dynamically-typed language |
| 2026-01-12 19:38:11 | × | trickard quits (~trickard@cpe-48-98-47-163.wireline.com.au) (Read error: Connection reset by peer) |
| 2026-01-12 19:38:24 | → | trickard_ joins (~trickard@cpe-48-98-47-163.wireline.com.au) |
| 2026-01-12 19:38:25 | × | Lord_of_Life quits (~Lord@user/lord-of-life/x-2819915) (Ping timeout: 264 seconds) |
| 2026-01-12 19:38:55 | Lord_of_Life_ | is now known as Lord_of_Life |
| 2026-01-12 19:39:05 | <tomsmeding> | (notable side track: if the language you're interpreting is itself typed and the AST you're interpreting is type-indexed in haskell too, you can write a strongly-typed interpreter that doesn't need such "tags" (data constructors)) |
| 2026-01-12 19:39:16 | <Enrico63> | "if you implement an interpreter for a language, then how do you represent the values computed in the language you're interpreting?" Can you explain? It isn't obvious to me |
| 2026-01-12 19:40:42 | <EvanR> | can I get a reaction to your eval question. |
| 2026-01-12 19:40:46 | <EvanR> | > ord 'a' |
| 2026-01-12 19:40:47 | <lambdabot> | 97 |
| 2026-01-12 19:40:55 | <EvanR> | > chr 'a' |
| 2026-01-12 19:40:56 | <lambdabot> | Couldn't match expected type ‘Int’ with actual type ‘Char’ |
| 2026-01-12 19:40:56 | <lambdabot> | In the first argument of ‘chr’, namely ‘'a'’ |
| 2026-01-12 19:40:56 | <lambdabot> | In the expression: chr 'a' |
| 2026-01-12 19:41:08 | <EvanR> | it's evalling the 'string' but it's not a dynamically typed language |
| 2026-01-12 19:41:56 | <EvanR> | the string ord 'a' |
| 2026-01-12 19:42:09 | <Enrico63> | What is the language under > ? |
| 2026-01-12 19:42:29 | × | trickard_ quits (~trickard@cpe-48-98-47-163.wireline.com.au) (Read error: Connection reset by peer) |
| 2026-01-12 19:42:31 | <Enrico63> | Haskell? |
| 2026-01-12 19:42:37 | × | euphores quits (~SASL_euph@user/euphores) (Quit: Leaving.) |
| 2026-01-12 19:42:48 | <tomsmeding> | Enrico63: https://play.haskell.org/saved/nOqh7N84 this is a little interpreter for a stupid little language that only has integers and pairs |
| 2026-01-12 19:42:53 | <Enrico63> | Oh, ord is not in prelude, that's why I don't see it in ghci :D |
| 2026-01-12 19:42:55 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 264 seconds) |
| 2026-01-12 19:43:05 | <EvanR> | import Data.Char |
| 2026-01-12 19:43:45 | <tomsmeding> | Enrico63: note Value in my snippet: to represent the value morally written "(2, 3)", I have to write "VPair (VI 2) (VI 3)" |
| 2026-01-12 19:43:59 | <Enrico63> | Yeah, EvanR, yeah. Given what it is I thought it was in Prelude, but when I tried in ghci I got the error, and questioned myself why I was giving for granted it was Haskell. |
| 2026-01-12 19:44:05 | <tomsmeding> | the VPair takes the place of the (,), but the VI are strictly extra compared to the "typed" representation "(2, 3)" |
| 2026-01-12 19:44:58 | <tomsmeding> | this Value is quite dynamic: given a Value, we only know what grammar values follow, but inside that there may be anything |
| 2026-01-12 19:45:16 | <tomsmeding> | it's not very "typed" |
| 2026-01-12 19:46:11 | <tomsmeding> | in a dynamically typed language, there'd be no need for the VI as you'd just stuff integers in pairs and call it a day |
| 2026-01-12 19:47:18 | <EvanR> | .oO( there could plausibly be a language where you don't need the VI but it's not dynamically typed ) |
| 2026-01-12 19:48:06 | <tomsmeding> | this dynamic/static typing is very much "I know it when I see it" terminology; it's hard to impossible (and probably unproductive) to give a strict definition, but particular languages are generally agreed to be either the one or the other |
| 2026-01-12 19:48:33 | → | trickard_ joins (~trickard@cpe-48-98-47-163.wireline.com.au) |
| 2026-01-12 19:49:02 | <tomsmeding> | bonus treat: by making the embedded language strongly typed, we can remove some of the dynamism https://play.haskell.org/saved/a3yS25TF |
| 2026-01-12 19:49:03 | <EvanR> | for a long time I felt the distinction comes down to not really the language itself but the workflow, your shit is checked immediately or at the last possible minute |
| 2026-01-12 19:49:32 | <tomsmeding> | (note that the error cases in 'interpret' are gone!) |
| 2026-01-12 19:50:10 | <EvanR> | for instance, elixir is very dynamic, but is currently being heavily checked in the IDE using what they're calling "a type system" |
| 2026-01-12 19:50:21 | <EvanR> | so it's getting pretty similar in some way to a static language |
| 2026-01-12 19:50:37 | <EvanR> | but it's the same language as before |
| 2026-01-12 19:51:47 | <tomsmeding> | Enrico63: remember that Alexis' point was that you can simulate dynamic typing in a statically-typed language by deferring checks until the last possible moment (see my first 'interpret' function that errors at the last possible moment), and that you generally do _not_ want to do this except for the specific case of writing an interpreter |
| 2026-01-12 19:51:56 | → | euphores joins (~SASL_euph@user/euphores) |
| 2026-01-12 19:52:07 | <tomsmeding> | her point was not that you must use a dynamically-typed language here |
| 2026-01-12 19:53:24 | <tomsmeding> | that is: if you're parsing JSON with aeson, you generally *don't* want to parse to a Value ( https://hackage.haskell.org/package/aeson-2.2.3.0/docs/Data-Aeson.html#t:Value ) and deal with wrong keys/types later |
| 2026-01-12 19:53:24 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-01-12 19:53:40 | <tomsmeding> | but exceptions to that "generally" exist, and an interpreter is one of them |
| 2026-01-12 19:55:00 | <tomsmeding> | (is there a Rice's theorem for rules? Every rule that says something nontrivial has exceptions) |
| 2026-01-12 19:55:43 | <EvanR> | someone's law: all rules have exceptions, including someone's law |
| 2026-01-12 19:56:11 | <tomsmeding> | indeed, the trivial rules |
| 2026-01-12 19:56:41 | <tomsmeding> | thus we conclude that all rules with no exceptions are trivial |
| 2026-01-12 19:57:43 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 240 seconds) |
| 2026-01-12 19:57:57 | <Enrico63> | I'm re-reading from the beginning :| |
| 2026-01-12 20:00:37 | × | vanishingideal quits (~vanishing@user/vanishingideal) (Ping timeout: 264 seconds) |
| 2026-01-12 20:00:57 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-01-12 20:03:35 | × | sp1ff quits (~user@2601:1c2:4701:900::327f) (Read error: Connection reset by peer) |
| 2026-01-12 20:07:35 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 240 seconds) |
| 2026-01-12 20:11:40 | × | Jackneill quits (~Jackneill@94-21-195-8.pool.digikabel.hu) (Read error: Connection reset by peer) |
| 2026-01-12 20:11:42 | → | Jackneill_ joins (~Jackneill@94-21-195-8.pool.digikabel.hu) |
| 2026-01-12 20:13:25 | → | merijn joins (~merijn@host-cl.cgnat-g.v4.dfn.nl) |
| 2026-01-12 20:17:11 | <Enrico63> | tomsmeding, I see that in the two code snippets you have an interpreter for a weakly vs strongly typed language. If nothing else, I can `interpret $ Plus (Pair (I 2) (I 3)) (I 4)`, which errors at runtime vs fails to compile. Understood. But when you write "this Value is quite dynamic: given a Value, we only know what grammar values follow, but |
| 2026-01-12 20:17:11 | <Enrico63> | inside that there may be anything", what does that exactly mean? Is it that given a Value (in the first snippet) I have to match on constructors (something that happens at runtime), whereas Value (in the second snippet), being parametric carries information in its type? |
| 2026-01-12 20:17:44 | × | spew quits (~spew@user/spew) (Quit: nyaa~) |
| 2026-01-12 20:18:16 | × | merijn quits (~merijn@host-cl.cgnat-g.v4.dfn.nl) (Ping timeout: 246 seconds) |
| 2026-01-12 20:18:56 | → | jmcantrell_ joins (~weechat@user/jmcantrell) |
| 2026-01-12 20:19:38 | × | Googulator quits (~Googulato@2a01-036d-0106-4994-d043-6d2a-58f7-29ea.pool6.digikabel.hu) (Quit: Client closed) |
| 2026-01-12 20:19:39 | → | jzargo joins (~jzargo@user/jzargo) |
| 2026-01-12 20:19:54 | → | Googulator joins (~Googulato@2a01-036d-0106-4994-d043-6d2a-58f7-29ea.pool6.digikabel.hu) |
| 2026-01-12 20:27:00 | <tomsmeding> | Enrico63: the second snippet is more of a side-note that muddles the waters in this discussion |
| 2026-01-12 20:27:38 | <tomsmeding> | the point in the first snippet is that when interpreting 'Fst e', I have to pattern-match on the result of 'interpret e' to check if it is VPair or something else |
| 2026-01-12 20:28:16 | <Enrico63> | Ok. I see that. |
| 2026-01-12 20:28:33 | <tomsmeding> | this is a check at the last possible time: there is some kind of assumption on the shape of this value (because Fst requires a pair), but our data representation does not encode that assumption |
| 2026-01-12 20:29:10 | <tomsmeding> | normally, in a statically typed language, we try to avoid this: when you use 'fst' in haskell, you'd rather like the type system to ensure that the argument to 'fst' is indeed a pair! |
All times are in UTC.