11 ms·
OCaml as my primary language
- moi2388 1y agoIf I wanted to program in OCaml, id program in F# instead
- nukifw 1y agoHi! Thank you for your interest (and for potentially reading this). Yes, F# is a very nice language, however, it seems to me that I am making a somewhat forced comparison between OCaml and F# in the following section: https://xvw.lol/en/articles/why-ocaml.html#ocaml-and-f https://xvw.lol/en/articles/why-ocaml.html#ocaml-and-f
- Smaug123 1y agoYou can hack up GADTs in F# - for example, https://github.com/Smaug123/WoofWare.Incremental/blob/9b8181ca1075102b1f62b06b82c8bb83c17f8f17/WoofWare.Incremental/Types.fs#L223 https://github.com/Smaug123/WoofWare.Incremental/blob/9b8181... (which uses https://github.com/G-Research/TypeEquality https://github.com/G-Research/TypeEquality ). (The other missing features I agree are missing.)
- nukifw 1y agoYes, the trick is expanded here: https://libres.uncg.edu/ir/asu/f/Johann_Patricia_2008_Foundations_For_Structured_Programming_with_gadts.pdf https://libres.uncg.edu/ir/asu/f/Johann_Patricia_2008_Founda... (if you have `Eq a b = Refl : a a eq` you should be able to encode every useful GADTs. But having a compiler support is nice for specifics reason like being able to "try" to detect unreachable cases in match branches for examples.
- debugnik 1y agoI've used equality witnesses in F# before, they kinda work but can't match proper GADTs. First you'll need identity conversion methods on the witness, because patterns can't introduce type equalities, then you'll realise you can't refute unreachable branches for the same reason, so you still need to use exceptions.
- moi2388 1y agoThanks, that was an interesting read for sure!
- jimbob45 1y agoIt's always weird when Microsoft pulls an "embrace, extend, extinguish" but the "extinguish" part happens without their involvement or desire and then we're all stuck wondering how Microsoft got left holding the bag.
- shortrounddev2 1y agoOCaml is a great language without great tooling. Desperately needs a good LSP implementation to run breakpoints and other debugging tools on VSCode or other LSP-aware IDEs. I know there ARE tools available but there isn't great support for them and they don't work well
- nukifw 1y agoIndeed, efforts should be made in terms of DAP (https://microsoft.github.io/debug-adapter-protocol// https://microsoft.github.io/debug-adapter-protocol//), extending the following experimentation: https://lambdafoo.com/posts/2024-03-25-ocaml-debugging-with-emacs.html https://lambdafoo.com/posts/2024-03-25-ocaml-debugging-with-.... However, I find the assertion about tooling a bit exaggerated, don't you?
- lambda_foo 1y agoUsing DAP with VSCode and OCaml's bytecode debugger (https://github.com/hackwaly/ocamlearlybird https://github.com/hackwaly/ocamlearlybird) works reasonably well already. My blog post above can be modified to work with VSCode, but I'm primarily an Emacs user. The main issue with Emacs and DAP is the DAP modes are not very polished. The missing piece for OCaml is debugging native code, the compiler doesn't emit enough DWARF information and debuggers like LLDB or GDB, need to be modified to understand OCaml's DWARF information. Even there DAP with LLDB/GDB works, it's just the debugger doesn't have enough information to work well. You end up working with debugging assembly or C (if it's parts of the runtime). I've written up a PR documenting native debugging here https://github.com/ocaml/ocaml/pull/13747 https://github.com/ocaml/ocaml/pull/13747.
- dismalaf 1y ago?? OCaml has had a completion engine for as long as I can remember (definitely over a decade) and it powers their LSP these days. I do know however that the community focuses mostly on Vim and Emacs.
- debugnik 1y agoLSP isn't the protocol that interfaces with debuggers, that'd be DAP. You're right that OCaml debugging is kinda clunky at the moment. OCaml does have an okay LSP implementation though, and it's getting better; certainly more stable than F#'s in my experience, since that comparison is coming up a lot in this comment section.
- ackfoobar 1y ago> Sum types: For example, Kotlin and Java (and de facto C#) use a construct associated with inheritance relations called sealing. This has the benefit of giving you the ability to refer to a case as its own type. > the expression of sums verbose and, in my view, harder to reason about. You declare the sum type once, and use it many times. Slightly more verbose sum type declaration is worth it when it makes using the cases cleaner.
- wiseowise 1y ago> Slightly more verbose sum type declaration is worth it *when it makes using the cases cleaner.* Correct. This is not the case when you talk about Java/Kotlin. Just ugliness and typical boilerplate heavy approach of JVM languages.
- gf000 1y agoYou mistyped "backwards compatible change" going back to close to 3 decades.
- ackfoobar 1y ago> Just ugliness and typical boilerplate heavy approach of JVM languages. I have provided a case how using inheritance to express sum types can help in the use site. You attacked without substantiating your claim.
- wiseowise 1y agoKotlin's/Java's implementation is just a poor man's implementation of very restricted set of real sum types. I have no idea what > This has the benefit of giving you the ability to refer to a case as its own type. means.
- ackfoobar 1y ago> I have no idea I can tell. Thankfully the OCaml textbook has this explicitly called out. https://dev.realworldocaml.org/variants.html#combining-records-and-variants https://dev.realworldocaml.org/variants.html#combining-recor... > The main downside is the obvious one, which is that an inline record can’t be treated as its own free-standing object. And, as you can see below, OCaml will reject code that tries to do so.
- _mu 1y agoI haven't worked in OCaml but I have worked a bit in F# and found it to be a pleasant experience. One thing I am wondering about in the age of LLMs is if we should all take a harder look at functional languages again. My thought is that if FP languages like OCaml / Haskell / etc. let us compress a lot of information into a small amount of text, then that's better for the context window. Possibly we might be able to put much denser programs into the model and one-shot larger changes than is achievable in languages like Java / C# / Ruby / etc?
- nukifw 1y agoTo be completely honest, I currently only use LLMs to assist me in writing documentation (and translating articles), but I know that other people are looking into it: https://anil.recoil.org/wiki?t=%23projects https://anil.recoil.org/wiki?t=%23projects
- d4mi3n 1y agoI think this is putting the cart before the horse. Programs are generally harder to read than they are to write, so optimizing for concise output to benefit the tool at the potential expense of the human isn't a trade I'd personally make. Granted, this may just be an argument for being more comfortable reading/writing code in a particular style, but even without the advantages of LLMs adoption of functional paradigms and tools has been a struggle.
- gf000 1y agoMy completely non-objective experiment of writing a simple CLI game in C++ and Haskell shows that the lines of code were indeed less in case of Haskell.. but the number of words were roughly the same, meaning the Haskell code just "wider" instead of "higher". And then I didn't even make this "experiment" with Java or another managed, more imperative language which could have shed some weight due to not caring about manual memory management. So not sure how much truth is in there - I think it differs based on the given program: some lend itself better for an imperative style, others prefer a more functional one.
- QuadmasterXLII 1y ago
- raphinou 1y agoSome years ago I also wanted to make ocaml my primary language, but rapidly encountered problems: difficulty to install (on Linux due to the requirement of a very unusual tool which name and function I forgot), no response from community regarding how to solve that problem, no solid postgresql driver, .... Wanting to use a functional language I pivoted to fsharp, which was not the expected choice for me as I use Linux exclusively. I have been happy with this choice, it has even become my preferred language. The biggest problem for me was the management of the fsharp community, the second class citizen position of fsharp in the DotNet ecosystem, and Microsoft's action screwing the goodwill of the dev community (eg hot reload episode). I feel this hampered the growth of the fsharp community. I'm now starting to use rust, and the contrast on these points couldn't be bigger. Edit: downvoters, caring to share why? I thought sharing my experience would have been appreciated. Would like to know why I was wrong.
- johnisgood 1y ago> difficulty to install Use opam: https://opam.ocaml.org https://opam.ocaml.org or https://opam.ocaml.org/doc/Install.html https://opam.ocaml.org/doc/Install.html. Additionally, see: https://ocaml.org/install#linux_mac_bsd https://ocaml.org/install#linux_mac_bsd and https://ocaml.org/docs/set-up-editor https://ocaml.org/docs/set-up-editor. It is easy to set up with Emacs, for example. VSCodium has OCaml extension as well. All you need for the OCaml compiler is opam, it handles all the packages and the compiler. For your project, use dune: https://dune.readthedocs.io/en/stable/quick-start.html https://dune.readthedocs.io/en/stable/quick-start.html.
- Milpotel 1y ago"use opam" is always the answer but in reality its the worst package manager ever. I've never seen so many packages fail to install, so many broken dependencies and miscompilations that resulted in segfaults due to wrong dependencies. I just gave up with Ocaml due to the crappy ecosystem, although I could have lived with the other idiosyncrasies.
- johnisgood 1y ago
- loxs 1y agoI migrated from OCaml to Rust around 2020, haven't looked back. Although Rust is quite a lot less elegant and has some unpleasant deficiencies (lambdas, closures, currying)... and I end up having to close one one eye sometimes and clone some large data-structure to make my life easier... But regardless, its huge ecosystem and great tooling allows me to build things comparatively so easily, that OCaml has no chance. As a bonus, the end result is seriously faster - I know because I rewrote one of my projects and for some time I had feature parity between the OCaml and Rust versions. Nevertheless, I have fond memories of OCaml and a great amount of respect for the language design. Haven't checked on it since, probably should. I hope part of the problems have been solved.
- ackfoobar 1y ago> the end result is seriously faster Do you have a ballpark value of how much faster Rust is? Also I wonder if OxCaml will be roughly as fast with less effort.
- deleted 1y ago[deleted]
- loxs 1y agoJust the straight/naive rewrite was ~3 times faster for my benchmark (which was running the program on the real dataset) and then I went down the rabbit hole and optimized it further and ended up ~5 times faster. Then slapped Rayon on top and got another ~2-3x depending on the number of cores and disk speed (the problem wasn't embarrassingly parallel, but still got a nice speedup). Of course, all of this was mostly unneeded, but I just wanted to find out what am I getting myself into, and I was very happy with the result. My move to Rust was mostly not because of speed, but I still needed a fast language (where OCaml qualifies). This was also before the days of multicore OCaml, so nowadays it would matter even less.
- ackfoobar 1y ago> straight/naive rewrite was ~3 times faster How much of that do you think comes from reduced allocations/indirections? Now I really want to try out OxCaml and see if I can approximate this speedup by picking up low hanging fruits.
- FrustratedMonky 1y agoIn F# comparison. Modules "my opinion, strongly justify preferring one over the other". Strong stance on Modules. My ignorance, what do they do that provides that much benefit. ??
- debugnik 1y agoIn short, OCaml modules are used for coarse-grained generics. Modules are like structurally-typed records that can contain both abstract types and values/functions dependent on those types; every implementation file is itself a module. When passed to functors (module-level functions), they allow you to parameterize large pieces of code, depending on multiple types and functions, all at once quite cleanly. And simply including them or narrowing their signatures is how one exports library APIs. (The closest equivalent I can imagine to module signatures is Scala traits with abstract type members, but structurally-typed and every package is an instance.) However, they are a bit too verbose for finer-grained generics. For example, a map with string keys needs `module String_map = Map.Make(String)`. There is limited support for passing modules as first-class values with less ceremony, hopefully with more on the way.
- akkad33 1y agoI don't know OCAML well but I think this is referring to the fact that modules on OCAML can be generic. In f# there is no HKTs that is types that can be parameterised with type classes. So in F# you have to have List.map, option.map etc, whereas in a language like OCAML or Haskell they would have one parametrised module
- noelwelsh 1y agoI saw a talk by someone from Google about their experiences using Rust in the Android team. Two points stuck out: they migrated many projects from Python, so performance can't have been that much of a concern, and in their surveys the features people liked most were basics like pattern matching and ADTs. My conclusion is that for a lot of tasks the benefit from Rust came from ML cicra 1990, not lifetimes etc. I feel if OCaml had got its act together around about 2010 with multicore and a few other annoyances[1] it could have been Rust. Unfortunately it fell into the gap between what academia could justify working on and what industry was willing to do. [1]: Practically speaking, the 31-bit Ints are annoying if you're trying to do any bit bashing, but aesthetically the double semicolons are an abomination and irk me far more.
- unstruktured 1y agoThere is absolutely no reason to use double semicolons in practice. The only place you really should see it is when using the repl.
- sigzero 1y agoYeah, it makes me think he doesn't understand them in OCaml.
- acjohnson55 1y agoI worked in OCaml for a year and I couldn't tell you by memory what the difference was. I remember being very annoyed by OCaml's many language quirks.
- benreesman 1y ago[flagged]
- simonask 1y agoThis is one of the wilder conspiracy theories to me, and that says a lot in 2025.
- nine_k 1y agoI wish somebody with this amount of experience would compare the benefits / shortcomings of using the ReasonML syntax. (The article mentions it once, in passing.)
- hardwaregeek 1y agoI like Reason syntax and I wish it was more common, but I think if you want to engage in the OCaml community it’s probably better to just bite the bullet and use the standard syntax. It’s what almost everybody uses so you’ll need to understand it to read any code or documentation in the ecosystem
- myaccountonhn 1y agoI don't have extensive experience but the little I did was issues with LSP not working as well.
- nukifw 1y agoSorry, I never used ReasonML so I don't see any advantage of using ReasonML except it had the time to die twice in 4 years :)
- globuous 1y agoWhat I missed most were let bindings (https://ocaml.org/manual/5.3/bindingops.html https://ocaml.org/manual/5.3/bindingops.html) ReasonML has custom operators that allows for manipulating monads somewhat sanely (>>= operators and whatnot). rescript (reasonml’s “fork”) did not last time I checked. But it does have an async/await syntax which helps a lot with async code. reasonml did not last time I checked, so you had to use raw promises. I believe Melange (which the article briefly talks about) supports let bindings with the reason syntax. And this kinda changes everything if you React. Because you can now have sane JSX with let bindings. Which you could not until melange. Indeed, you can PPX your way out of it in ocaml syntax, but I’m not sure the syntax highlight works well in code editors. It did not on mine anyway last time I checked. So for frontend coding, Melange’s reason ml is great as you have both, and let bindings can approximate quite well async syntax on top of writing readable monadic code. For backend code, as a pythonista, I hate curlies. and I do like parenthesis-less function calls and definitions a lot. But I still have a lot of trouble, as a beginner ocamler, with non-variable function argument as I need to do “weird” parenthesis stuff. Hope this “helps”!
- garbthetill 1y agoWhat a brilliant article, it really puts to rest for me, the whole “why not use F#?” argument. In almost every OCaml thread, someone suggests F# as a way to sidestep OCaml’s tooling. I’ve always been curious about OCaml, especially since some people call it “Go with types” and I’m not a fan of writing Rust. But I’m still not sold on OCaml as a whole, its evangelists just don’t win me over the way the Erlang, Ruby, Rust, or Zig folks do. I just cant see the vision
- debugnik 1y agoFunny, I moved to OCaml to sidestep F# tooling. At least last time I used F#: Slow compiler, increasingly C#-only ecosystem, weak and undocumented MSBuild (writing custom tasks would otherwise be nice!), Ionide crashes, Fantomas is unsound... But OCaml sadly can't replace F# for all my use cases. F# does get access to many performance-oriented features that the CLR supports and OCaml simply can't, such as value-types. Maybe OxCaml can fix that long term, but I'm currently missing a performant ML-like with a simple toolchain.
- JaggerJo 1y agoDid you try F# in JetBrains Rider? It's the best F# tooling you can buy IMO.
- debugnik 1y agoYes I actually ended up using Rider, although I don't like switching editors. But that only replaces Ionide, and I was having some growing pains with the entire toolchain.
- JaggerJo 1y agoYeah - I worked on an F# project that was ~300K LoC and tooling speed really becomes an issue at that point.
- joshmarlow 1y agoIt's been a few years since I've touched OCaml - the ecosystem just wasn't what I wanted - but the core language is still my favorite. And the best way I can describe why is that my code generally ends up with a few heavy functions that do too much; I can fix it once I notice it, but that's the direction my code tends to go in. In my OCaml code, I would look for the big function and... just not find it. No single workhorse that does a lot - for some reason it was just easier for me to write good code. Now I do Rust for side projects because I like the type system - but I would prefer OCaml. I keep meaning to checkout F# though for all of these reasons.
- manoDev 1y agoI'm sure there's merit to the language, but the syntax seems absolutely alien to me. Some attempt to look like verbose imperative code, a bunch of semicolons, and for some strange reason, hate of parenthesis. Real life sample: let print_expr exp = (* Local function definitions *) let open_paren prec op_prec = if prec > op_prec then print_string "(" in let close_paren prec op_prec = if prec > op_prec then print_string ")" in let rec print prec exp = (* prec is the current precedence *) match exp with Const c -> print_float c | Var v -> print_string v | Sum(f, g) -> open_paren prec 0; print 0 f; print_string " + "; print 0 g; close_paren prec 0 | Diff(f, g) -> open_paren prec 0; print 0 f; print_string " - "; print 1 g; close_paren prec 0 | Prod(f, g) -> open_paren prec 2; print 2 f; print_string " * "; print 2 g; close_paren prec 2 | Quot(f, g) -> open_paren prec 2; print 2 f; print_string " / "; print 3 g; close_paren prec 2 in print 0 exp;; A function is defined as: let print_expr exp = That seems pretty hard to read at a glance, and easy to mistype as a definition. Also, you need to end the declaration with `in`? Then, semicolons... open_paren prec 0; print 0 f; print_string " + "; print 0 g; ... and even double semicolons ... print 0 exp;; That looks like a language you really want an IDE helping you with.
- myaccountonhn 1y agoThat code is probably some of the hardest you'll encounter in Ocaml, but for me its quite obvious what it does and easy to read because I've worked with GADTs before. If you haven't then its you'll need to study and understand them to understand the code. I actually really like the syntax of OCaml, its very easy to write and when you're used to it, easy to read (easier than reasonml IMO). Double semicolons are afaik only used in the repl.
- UncleOxidant 1y agoI mean, I guess it depends on your background, but that code looks pretty nice compared to how it would look in a language without pattern matching and ADTs. This is why the MLs excel for things like parsers, interpreters, and compilers. Beauty is in the eye of the beholder, I guess. I suspect that if you gave it a bit of time it would start to really grow on you - that's how it was in my case. At first: "WTF is this weird syntax?!", a few weeks in "oh, this makes a lot of sense, actually", a few years "Yeah, I'd much rather write this sort of thing in OCaml (or an ML in general)"
- jasperry 1y agoQuestion about terminology: Is it common to call higher-order function types "exponential types" as the article does? I know what higher-order functions are, but am having trouble grasping why the types would be called "exponential".
- nukifw 1y agoUsually we speaking only about sum and product (because article usually refers to ADT, so Algebraic Data type). A function is not really Data, so it is not included. But you can use the same tricks (ie: a -> b has arity b^a) to compute the number of potential inhabitant
- ackfoobar 1y agoA first-order function type is already exponential. A sum type has as many possible values as the sum of its cases. E.g. `A of bool | B of bool` has 2+2=4 values. Similarly for product types and exponential types. E.g. the type bool -> bool has 2^2=4 values (id, not, const true, const false) if you don't think about side effects.
- jolmg 1y ago> bool -> bool has 2^2=4 values Not the best example since 2*2=4 also. How about this bit of Haskell: f :: Bool -> Maybe Bool That's 3 ^ 2 = 9, right? f False = Nothing f False = Just True f False = Just False f True = Nothing f True = Just True f True = Just False Those are 6. What would be the other 3? or should it actually be a*b=6? EDIT: Nevermind, I counted wrong. Here are the 9: f x = case x of True -> Nothing False -> Nothing f x = case x of True -> Nothing False -> Just False f x = case x of True -> Nothing False -> Just True f x = case x of True -> Just False False -> Nothing f x = case x of True -> Just False False -> Just False f x = case x of True -> Just False False -> Just True f x = case x of True -> Just True False -> Nothing f x = case x of True -> Just True False -> Just False f x = case x of True -> Just True False -> Just True
- 1y ago
- zem 1y agoocaml is one of my favourite languages too, but I've found myself being drawn towards rust for my latest project due to its major superpower - you can write a rust library that looks like a c library from the outside, and can be called from other languages via their existing c ffi mechanisms. I feel like by writing the library in ocaml I would have a better experience developing it, but be giving up on that free interop.
- pmahoney 1y agoI tried to like OCaml for a few years. The things that hold me back the most are niggling things that are largely solved in more "modern" langs, the biggest being the inability to "print" arbitrary objects. There are ppx things that can automatically derive "to string" functions, but it's a bit of effort to set up, it's not as nice to use as what's available in Rust, and it can't handle things like Set and Map types without extra work, e.g. [1] (from 2021 so situation may have changed). Compare to golang, where you can just use "%v" and related format strings to print nearly anything with zero effort. [1] https://discuss.ocaml.org/t/ppx-deriving-implementation-for-pretty-printing-sets-maps/9004 https://discuss.ocaml.org/t/ppx-deriving-implementation-for-...
- throwaway127482 1y agoGo's %v leaves a lot to be desired, even when using %+#v to print even more info. I wish there was a format string to deeply traverse into pointers. Currently I have to import go-spew for that, which is a huge annoyance. Python does it best from what I've seen so far, with its __repr__ method.
- tucnak 1y agoGo has both Stringer and GoStringer interfaces, which is basically the same thing as __repr__.
- jerf 1y agoThe default %v does leave some to be desired, but don't underestimate the utility of being able to shove anything at it and get something back. This is especially important because this applies recursively; you can have a structure that may have something "unprintable" buried deeply in it, but at least it won't prevent you from printing everything else. Strongly-typed languages that do not force any sort of stringification on values, and thus refuse to compile if you try to dump a simple log message of one of these values out, are really annoying to work with. I understand the conceptual purity of saying "Hey, maybe not everything even has a string representation" but it makes debugging a real pain. If I were writing a new language today I think I'd mandate that everything gets a default debugging string output by default because the alternative is just so rough. Even a not-great printer that may have a sort of "*unprintable*" bailout, or print something not terribly useful, but doesn't actually stop you from printing anything, is better than a language that completely rejects it at compile time.
- vram22 1y agoIs OCaml somewhat suitable for desktop GUI app programming? I saw this in the OP: >For example, creating a binding with the Tk library and had also been thinking about this separately a few days ago, hence the question.
- mhitza 1y ago> Is OCaml somewhat suitable for desktop GUI app programming? No. Unless you also want to be the maintainer of a UI bindings library.
- pshirshov 1y agoExtremely dated. No HKTs, no typeclasses (modules are not a good substitute), no call-site expansion.
- nukifw 1y ago- Extremely dated: we have almost one new release every six month and in recent releases, the language runtime has been changed and user-defined effects have been introduced. - No HKTs "in your sense" but: ```ocaml module type S = sig type 'a t end `` `type 'a t` is an Higher Kinded type (but in the module Level). - No typeclasses, yes, for the moment but the first step of https://arxiv.org/pdf/1512.01895 https://arxiv.org/pdf/1512.01895 is under review: https://github.com/ocaml/ocaml/pull/13275 https://github.com/ocaml/ocaml/pull/13275 - no call-site expansion ? https://ocaml.org/manual/5.0/attributes.html https://ocaml.org/manual/5.0/attributes.html look at the attribute `inline`.
- grumpyprole 1y agoTrue, but then concurrency via algebraic effects makes it look more modern than Rust.
- abathologist 1y agoAlmost as if the development of PLs is not a straight line of monotonically additive features XD
- deleted 1y ago[deleted]
- chris_armstrong 1y agoAs someone who uses OCaml for hobby projects, I appreciate how little the language gets in your way when you want to just “get shit done”, despite the language’s origins in academia and industrial uses. The type system usually means that I might take longer to get my code to compile, but that I won’t spend much (if any) time debugging it once I’m done. I’m in the middle of pulling together bits of a third party library and refactoring them over several days work, and I’m pretty confident that most of the issues I’ll face when done will be relatively obvious runtime ones.
- chris_armstrong 1y agoI almost never find a use for GADTs or functors or carefully specifying module types, but when I need them, they help me get stuff done neatly. Even the object system which most OCaml developers avoid, is actually very useful for some specific modelling scenarios (usually hierarchies in GUIs or IaC) that comes with similar type system guarantees and minimal type annotations.
- birdfood 1y agoOCaml is probably my favourite language. The most involved project I did with it was a CRUD app for organising Writer's Festivals. The app was 100% OCaml (ReasonML so I could get JSX) + Dream + HTMX + DataTables. I used modules to get reusable front end templates. I loved being able to make a change to one of my data models and have the compiler tell me almost instantly where the change broke the front end. The main value of the app was getting data out of excel into a structured database, but I was also able to provide templated and branded itineraries in .odt format, create in memory zipped downloads so that I didn't need to touch the server disk. I was really impressed by how much I could achieve with the ecosystem. But having to write all my database queries in strings and then marshal the data through types was tiring (and effectively not compile time type checked) and I had to roll my own auth. I often felt like I was having to work on things that were not core to the product I was trying to build. I've spent a few years bouncing around different languages and I think my take away is that there is no perfect language. They all suck in their own special way. Now I'm building an app just for me and I'm using Rails. Pretty much everything I've wanted to reach for has a good default answer. I really feel like I'm focused on what is relevant to the product I'm building and I'm thinking about things unrelated to language like design layout and actually shipping the thing.
- BenGosub 1y agoWhat is the idiomatic way to handle the results from the database in a strongly typed functional language?
- birdfood 1y agoI certainly can’t say whether this is idiomatic as I was working it all out myself. But I’d basically write a type for each operation (as I’d read elsewhere). And honestly this was a bit of a drag too. I really wanted some reflection to make generating this code more ergonomic. From memory I’d have types like these Author id: int, name: string, book_id: int NewAuthor name: string, book_id: int ViewAuthor name: string, book_title: string Author represents the data in the db, NewAuthor allows for an insert operation, and ViewAuthor is for showing the data to a user. You could argue for combing Author and NewAuthor and making id optional but I wanted to enforce at the type level that I was working with stored data without needing to check id everywhere.
- rybosome 1y agoI’d have liked to see the use of dependency injection via the effects system expanded upon. The idea that the example program could use pattern matching to bind to either test values or production ones is interesting, but I can’t conceptualize what that would look like with the verbal description alone. Also, I had no idea that the module system had its own type system, that’s wild.
- mrkeen 1y agoHaskeller here! > The idea that the example program could use pattern matching to bind to either test values or production ones is interesting, but I can’t conceptualize what that would look like with the verbal description alone. The article appears to have described the free monad + interpreter pattern, that is, each business-logic statement doesn't execute the action (as a verb), but instead constructs it as a noun and slots it into some kind of AST. Once you have an AST you can execute it with either a ProdAstVisitor or a TestAstVisitor which will carry out the commands for real. More specific to your question, it sounds like the pattern matching you mentioned is choosing between Test.ReadFile and Test.WriteFile at each node of the AST (not between Test.ReadFile and Prod.ReadFile.) I think the Haskell community turned away a little from free monad + interpreter when it was pointed out that the 'tagless final' approach does the same thing with less ceremory, by just using typeclasses. > I’d have liked to see the use of dependency injection via the effects system expanded upon. I'm currently doing DI via effects, and I found a technique I'm super happy with: At the lowest level, I have a bunch of classes & functions which I call capabilities, e.g FileOps (readTextFile, writeTextFile, ...) Logger (info, warn, err, ...) Restful (postJsonBody, ...) These are tightly-focused on doing one thing, and must not know anything about the business. No code here would need to change if I changed jobs. At the next level up I have classes & functions which can know about the business (and the lower level capabilities) StoredCommands (fetchStoredCommands) - this uses the 'Restful' capability above to construct and send a payload to our business servers. At the top of my stack I have a type called CliApp, which represents all the business logic things I can do, e.g. I associate CliApp to all its actual implementations (low-level and mid-level) using type classes: instance FileOps CliApp where readTextFile = readTextFileImpl writeTextFile = writeTextFileImpl ... instance Logger CliApp where info = infoImpl warn = warnImpl err = errImpl ... instance StoredCommands CliApp where fetchStoredCommands = fetchStoredCommandsImpl ... In this way, CliApp doesn't have any of 'its own' implementations, it's just a set of bindings to the actual implementations. I can create a CliTestApp which has a different set of bindings, e.g. instance Logger CliTestApp where info msg = -- maybe store message using in-memory list so I can assert on it? Now here's where it gets interesting. Each function (all the way from top to bottom) has its effects explicitly in the type system. If you're unfamiliar with Haskell, a function either having IO or not (in its type sig) is a big deal. Non-IO essentially rules out non-determinism. The low-level prod code (capabilites) are allowed to do IO, as signaled by the MonadIO in the type sig: readTextFileImpl :: MonadIO m => FilePath -> m (Either String Text) but the equivalent test double is not allowed to do IO, per: readTextFileTest :: Monad m => FilePath -> m (Either String Text) And where it gets crazy for me is: the high-level business logic (e.g. fetchStoredCommands) will be allowed to do IO if run via CliApp, but will not be allowed to do IO if run via CliTestApp, which for me is 'having my cake and eating it too'. Another way of looking at it is, if I invent a new capability (e.g. Caching) and start calling it from my business logic, the CliTestApp pointing at that same business logic will compile-time error that it doesn't have its own Caching implementation. If I try to 'cheat' by wiring the CliTestApp to the prod Caching (which would make my test cases non-deterministic) I'll get another compile-time error. Would it work in OCaml? Not sure, the article says: > Currently, it should be noted that effect propagation is not tracked by the type system
- tempodox 1y agoThe best bird's eye overview of OCaml I've seen yet. As a long-time OCaml user, I find it entirely fair.
- sparkie 1y ago> At present, I don’t know anyone who has seriously used languages like OCaml or Haskell and was happy to return to languages with less sophisticated type systems (though an interesting project can sometimes justify such a technological regression). Recovered typeaholic here. I still occasionally use OCaml and I primarily wrote F# and Haskell for years. I've been quite deep down the typing rabbit hole, and I used to scorn at dynamically typed languages. Now I love dynamic typing - but not the Python kind - I prefer the Scheme kind - latent typing. More specifically, the Kernel[1] kind, which is incredibly powerful. > I think the negative reputation of static type checking usually stems from a bad experience. I think this goes two ways. Most people's experience with dynamic typing is the Python kind, and not the Kernel kind. To be clear, I am not against static typing, and I love OCaml - but there are clear cases where static typing is the wrong tool - or rather, no static typing system is sufficient to express problems that are trivial to write correctly with the right dynamic types. Moreover, some problems are inherently dynamic. Take for example object-capabilities (aka, security done right). Capabilities can be revoked at any time. It makes no sense to try and encode capabilities into a static type system - but I had such silly thoughts when I was a typeaholic, and I regularly see people making the same mistake. Wouldn't it be better to have a type system which can express things which are dynamic by nature? And this is my issue with purely statically typed systems: They erase the types! I don't want to erase the types - I want the types to be available at runtime so that I can do things with them that I couldn't do at compile time - without me having to write a whole new interpreter. My preference is for Gradual Typing[2], which lets us use both worlds. Gradual typing is static typing with a `dynamic` type in the type system, and sensible rules for converting between dynamic and static types - no transitivity in consistency. People often mistake gradual typing with "optional typing" - the kind that Erlang, Python and Typescript have - but that's not correct. Those are dynamic first, with some static support. Gradual typing is static-first, with dynamic support. Haskell could be seen as Gradual due to the presence of `Data.Dynamic`, but Haskell's type system, while a good static type system, doesn't make a very good dynamic type system. Aside, my primary language now is C, which was the first language I learned ~25 years ago. I regressed! I came back to C because I was implementing a gradually typed language and F#/OCaml/Haskell were simply too slow to make it practical, C++/Rust were too opinionated and incompatible with what I want to achieve, and C (GNU dialect) let me have almost complete control over the CPU, which I need to make my own language good enough for practical use. After writing C for a while I learned to love it again. Manually micro-optimizing with inline assembly and SIMD and is fun! [1]:https://web.cs.wpi.edu/~jshutt/kernel.html https://web.cs.wpi.edu/~jshutt/kernel.html [2]:https://jsiek.github.io/home/WhatIsGradualTyping.html https://jsiek.github.io/home/WhatIsGradualTyping.html
- amelius 1y agoDo they have a decent GUI library / binding yet?
- JaggerJo 1y agoOCaml does not have what you'd call I fully featured GUI framework to my knowledge (besides rendering webpages). F# has FuncUI - based on Avalonia. All just possible because of the ecosystem. https://github.com/fsprojects/Avalonia.FuncUI https://github.com/fsprojects/Avalonia.FuncUI
- amelius 1y agoI'm kind of surprised though because a lot of languages have a Qt binding. Implementing this is only a small investment for a relatively big gain.
- JaggerJo 1y agoDarkLang which was initially written in OCaml eventually switched to F#. From what I remember the main reasons were the library ecosystem and concurrency. I'm know .NET in and out, so I might be biased. Most of the boring parts have multiple good solutions that I can pick from. I don't have to spend time on things that are not essential to the problem I actually want to solve. I've used F# professionally for multiple years and maintain a quite popular UI library written in it. But even with .NET there still are gaps because of the smaller F# language ecosystem. Not everything "just works" between CLR languages - sometimes it's a bit more complicated. The main point I'm trying to make is that going off the beaten path (C#) for example also comes with a cost. That cost might or might not be offset by the more expressive language. It's important to know this so you are not surprised by it. With OCaml it's similar I'd say. You get a really powerful language, but you're off the beaten path. Sure, there are a few companies using it in production - but their use case might be different than yours. On Jane Streets Threads and Signals podcast they often talk about their really specific use cases. 1: https://blog.darklang.com/new-backend-fsharp/ https://blog.darklang.com/new-backend-fsharp/
- danluvscock 1y ago[dead]