I wish Standard ML had…

I've been working with F# since July 2022, and it has been my first true experience with functional programming, as well as with ML-like programming languages. Boy, do I love writing it!

In all fairness, it was just a matter of time before I looked into Standard ML. It turns out that, currently, it's my favorite programming language. However, things are not that greener on the other side of the fence as I wrote on a previous article: [Thoughts on Standard ML as of 2023]({{< relref "thoughts-on-standard-ml-as-of-2023" >}}).

This is just a collection of thoughts I had during my experience writing Standard ML. I might come back here with solutions... or more problems in the future.

Better infix operators

It's kind of funny that I'm not a big fan of custom operators. Still, having operators such as |> and >> in the standard library really influences how the language ecosystem evolves. You could argue that implementing the pipe operator in Standard ML is as easy as "infix 3 |> fun x |> f = f x" and you would be totally correct! It's just frustrating that due to a limitation (see MLton's InfixingOperators) you are required to define the fixity when using it outside its scope definition.

structure Operator = struct
  infix 3 |> fun x |> f = f x
end

structure Example = struct
  open Operator
  (* I hate doing this :( *)
  infix 3 |>
end

Polymorphic print function

I know, I know... this might be a bit controversial. You see people asking about this all the time on discuss.ocaml.org: "How do I print X?" or "How do I debug this record?". In Standard ML, you can't do that either. Basically, you have to implement your own "toString" function for each type you want to print. Having a basic feature like F#'s printfn "%A" someObj for debugging purposes feels really good in comparison.

One workaround I found to address this issue is to use Poly/ML's makestring function, which takes any value and returns its string representation. The downside, though, is that you have to use their compiler during development.

Unicode support

That's it. Standard ML compilers usually provide an extension to allow UTF-8 byte sequences. However, it just doesn't feel nice to work with it. I would love to have something like Go's strings and runes!

Computation Expressions

This has more to do with my F# experience. Computation expressions are really powerful, give the documentation a read if you don't them. It makes "result type-driven" development really easy and much more!

See for yourself one example of the awesome FsToolkit.ErrorHandling library.

type LoginError = InvalidUser | InvalidPwd | Unauthorized of AuthError

let login (username : string) (password : string) : Result<AuthToken, LoginError> =
  result {
    // requireSome unwraps a Some value or gives the specified error if None
    let! user = username |> tryGetUser |> Result.requireSome InvalidUser

    // requireTrue gives the specified error if false
    do! user |> isPwdValid password |> Result.requireTrue InvalidPwd

    // Error value is wrapped/transformed (Unauthorized has signature AuthError -> LoginError)
    do! user |> authorize |> Result.mapError Unauthorized

    return user |> createAuthToken
  }