[HN Gopher] What was wrong with SML?
       ___________________________________________________________________
        
       What was wrong with SML?
        
       Author : rdpintqogeogsaa
       Score  : 63 points
       Date   : 2022-04-30 18:10 UTC (4 hours ago)
        
 (HTM) web link (blog.plover.com)
 (TXT) w3m dump (blog.plover.com)
        
       | eatonphil wrote:
       | I mentioned to Mark when I saw this, and he noted it in the
       | addendum at the end, that calling Standard ML dead is a bit too
       | much. I've written recently [0] about how active it surprisingly
       | is.
       | 
       | I also disagree that its failure to "succeed" has anything to do
       | with syntax or semantics and solely that it doesn't have a Jane
       | Street or any company publicly behind it.
       | 
       | [0] https://notes.eatonphil.com/standard-ml-in-2020.html
        
         | porcoda wrote:
         | I don't think it succeeded or failed. Languages don't need to
         | be wildly popular in industry to be valuable. I personally like
         | that industry ignores SML. Even for Ocaml I don't use the Jane
         | street core: I just use the stock language since it's small and
         | stable. I like that Jane street helps improve the core compiler
         | though.
        
         | mjd wrote:
         | I would like to point out that the article does not use the
         | words "succeed" or "fail", and makes no claim about whether SML
         | "succeeded" or "failed". Indeed I don't think that is a useful
         | question to ask.
         | 
         | My article was trying to address a much less nebulous issue:
         | what problems did I personally see with SML in the mid 1990s
         | that let me to abandon it at that time.
        
           | eatonphil wrote:
           | Sorry, I didn't mean to put words in your mouth. I may just
           | have gotten the wrong impression of your intent.
           | 
           | Any criticism of any language is valid and good to have!
        
       | fanf2 wrote:
       | There was a history of Standard ML published as part of HOPL4 a
       | couple of years ago - https://dl.acm.org/doi/10.1145/3386336 I
       | was particularly interested by the more recent history, after SML
       | '97, where there was plenty of interest in improving the language
       | and its library, but Milner insisted that there would be no
       | further changes, and the Definition was non-free so the others
       | could not build on it. (It has since become Free though its
       | source code was lost.)
       | 
       | Despite that there is/was an SML Basis Library project, and a
       | Successor ML project, but it looks like even the die hard fans
       | have drifted away https://smlfamily.github.io/
        
       | bzxcvbn wrote:
       | I'm not really convinced by the author's first example. While an
       | element of type bool is an instance of type a, an element of type
       | bool -> bool is _not_ an instance of type a - > a.
       | 
       | The issue is precisely an issue of variance, which is mentioned
       | in reference to Scala, but somehow it's glossed over. The type a
       | -> a is covariant in its second argument, but contravariant in
       | its first argument. As a result, you cannot "specialize" it to
       | bool -> bool, because specialization is really another name for
       | covariance.
       | 
       | Another name for contravariance is "generalization". If you ask
       | for something of type "bool -> b", and someone provides you with
       | some function f of type "a -> b", then you're happy. The map f is
       | indeed an instance of "bool -> b": it can eat anything, so it can
       | eat booleans. But with type a -> a, you cannot do what I just
       | did: type "a" is already the most general one, and you cannot do
       | better.
       | 
       | If SML accepted something of type "bool -> bool" for an instance
       | of type "a -> a", then it was a fundamental error. But this
       | doesn't mean that the whole thing should have been thrown out and
       | replaced with monads. In fact, I don't really get how monads have
       | anything to do with the problem at hand. If Haskell can prevent
       | the following code, then I don't see why SML couldn't have
       | prevented the authors' code:                   do m <- STRef id
       | m <- not            m 42
        
       | pjmlp wrote:
       | What was wrong is not having a killer application or an industry
       | giant pushing it.
       | 
       | Grammar and language semantic are details regarding language
       | adoption.
        
         | eatonphil wrote:
         | Yup, adoption has nothing to do with being a sensible language.
         | More often it seems to be completely inversely related.
        
           | exikyut wrote:
           | What are some sensible (coherent, load-bearing, force-
           | multiplying etc) tools that have terrible adoption?
           | 
           | Unreasonably open-ended question (suppose the scope is ML, or
           | perhaps FP in general, or maybe even wider) - but I'm very
           | curious.
        
             | pharmakom wrote:
             | I would say F#. It has most things you could want:
             | 
             | - Proper functional programming support
             | 
             | - Fast enough runtime
             | 
             | - Cross-platform and open-source
             | 
             | - Mainstream ecosystem
             | 
             | - Large corporate backer
             | 
             | - Compile to JS
             | 
             | - Commercial and open-source tooling options
             | 
             | ... and yet C# is far more popular. This is because
             | language adoption is driven by existing user base and other
             | network effects NOT the quality of the language itself.
        
             | youerbt wrote:
             | TLA+ or Nix comes to mind. By some notion of terrible I
             | guess.
        
             | eatonphil wrote:
             | Google "worse is better". :)
        
       | Athas wrote:
       | > Haskell's primary solution to this is to burn it all to the
       | ground. Mutation doesn't cause any type problems because there
       | isn't any.
       | 
       | This is true, but I think it is also misleading. Haskell has the
       | same problem if you use unsafePerformIO to create a polymorphic
       | IORef at top level. You can then use this IORef to subvert the
       | type system. I think this is something many Haskell programmers
       | are not fully aware of: unsafePerformIO doesn't just break
       | referential transparency; it can also fundamentally break memory
       | safety. Now, you may say that unsafePerformIO is obviously unsafe
       | (it's in the name!) and should never be used. But if you look at
       | many foundational Haskell libraries, you will find that they use
       | unsafePerformIO or similar functions internally, usually for
       | performance reasons. What are the rules that govern safe usage of
       | unsafePerformIO? As far as I can determine, these rules are
       | basically just GHC implementation details, and people often get
       | them wrong. And if you break these rules, you don't just get a
       | function that doesn't do what you expected - you may have
       | subverted memory safety entirely.
       | 
       | I think this is an interesting conundrum. Haskell makes much
       | stronger promises than SML, but if you break the rules, all bets
       | are completely off.
        
       | throwamon wrote:
       | A bit off-topic, but could someone ELI5 what a lattice is in this
       | context?
        
         | mjd wrote:
         | If you have two types, there should be a single type that
         | "joins" them, in the sense that you can understand both of the
         | original types as somehow being special cases of the join type.
         | 
         | A join is not necessarily a union, since the representations of
         | the three types might be completely different, and also because
         | the third type might contain many values that don't correspond
         | to anything in the two original types. (It might be much bigger
         | than the union.)
         | 
         | Mathematical lattices must also have "meets", which are like
         | joins except down instead of up. I'm not sure that meets are as
         | important as joins in this context.
        
         | layer8 wrote:
         | It refers to https://en.m.wikipedia.org/wiki/Lattice_(order),
         | with the elements of the lattice being the arithmetic types and
         | the order relation being the subtyping relation here. Given any
         | two types in the lattice, the lattice property then guarantees
         | that there exists a unique common (least) supertype (aka upper
         | bound, supremum) of the two types. Which means you can apply
         | the binary operation (e.g. addition) as defined for that common
         | supertype.
        
         | chombier wrote:
         | I think this refers to a system of types in which for any two
         | types there is also an union type and an intersection type in
         | the lattice.
        
       | tialaramex wrote:
       | SML was the First Language used for the Computer Science degree I
       | took. I felt at that time, and continue to feel years later (that
       | degree course now teaches Java as First Language) that this was a
       | good decision _despite_ the fact that most graduates don 't end
       | up using SML to write anything.
       | 
       | In the course of my education I experienced some things which I'm
       | convinced are a _bad idea_ even though they worked out OK for me
       | such as selective education (whole schools only for  "talented"
       | children) and single sex secondary education, but SML as First
       | Language is not one of those things. It worked well for me _and_
       | I 'm convinced it's a good idea even though I would not advocate
       | writing new real world projects in SML unless you've got some
       | very particular reason.
        
       | chombier wrote:
       | > Scala has a very different solution to this problem, called
       | covariant and contravariant traits.
       | 
       | I thought Scala had an even stricter value restriction than ML,
       | where only function/methods may get a polymorphic type?
        
       | eatonphil wrote:
       | For folks interested in learning Standard ML, check out /r/sml
       | [0] and the sticky post [1] with a getting started guide.
       | 
       | [0] http://reddit.com/r/sml
       | 
       | [1]
       | https://www.reddit.com/r/sml/comments/qyy2gs/getting_started...
        
       | jasonhansel wrote:
       | IMHO Haskell's lazy evaluation has some significant disadvantages
       | compared to SML's strict evaluation. In particular, lazy
       | evaluation makes it difficult to find the performance bottlenecks
       | in a particular piece of code or to determine the time complexity
       | of an algorithm just by reading it.
       | 
       | Furthermore, subtle changes in how a function is written (for
       | instance, making a multiplication function not evaluate the right
       | operand if the left operand is zero) can cause wildly unexpected
       | performance changes in that function's callers. In effect, the
       | performance of a function is no longer just determined by that
       | function's structure and by the function calls it contains;
       | performance of one function now depends heavily on the
       | implementation details of others and the context in which that
       | function is used.
       | 
       | Granted, any optimizing compiler can have this effect, but it's
       | rarely noticeable in strictly-evaluated languages, where at least
       | to some extent the order of evaluation must correspond to the
       | structure of the code.
        
       | porcoda wrote:
       | I still actively use SML - mlton or smlnj usually, polyml too.
       | I'm aware of the issues raised in this post but haven't ever
       | found them to be a source of much headache. To be honest, the
       | biggest headache is moving between compilers and their different
       | build processes. Other than that, the fact that the language
       | isn't really changing is a big attraction for me.
       | 
       | CakeML is also a very cool project in SML land.
        
       | shpongled wrote:
       | SML is one of my favorite languages (I've been (very) slowly
       | writing a compiler & language server for it).
       | 
       | Sure, it has some warts/differences compared to newer languages -
       | we have moved towards traits/typeclasses/etc, and I wish I could
       | just write #[derive(Debug) - but I feel that SML fits in a very
       | unique spot for programming languages. It's extremely simple, yet
       | still powerful and expressive. I hope we will see continued work
       | on SML/Successor ML descendants (like 1ML, etc), because I think
       | there's still potential there
       | 
       | I think some updated language tooling would dramatically help.
        
         | jaytaylor wrote:
         | Is your development progress taking place in the open? (e.g.
         | GitHub or somewhere similar)
         | 
         | It sounds like a cool project and I'd love to see it! And even
         | have the option to open a PR and help ;)
        
           | eatonphil wrote:
           | Yes, please share or open-source it even if it's not done!
        
             | shpongled wrote:
             | You have already linked to it :)
        
           | shpongled wrote:
           | I have the compiler on GitHub [1] - I just started working on
           | it again after a 2-year hiatus (to finish my PhD).
           | 
           | As for language-server, I'm currently sketching out some
           | plans to use SMLnj's "Visible Compiler" feature, since that
           | seems the easiest path forward. I have a half-baked language-
           | server based on MLton's def-use output, but it's too unstable
           | to share. I am planning to make some progress on the
           | language-server in the next couple weeks.
           | 
           | [1] You'll notice it's just a fragment of the language for
           | now, and only half-implemented.
           | https://github.com/SomewhatML/sml-compiler and
           | https://github.com/SomewhatML/sml-analyzer (again for a
           | fragment of SML)
        
       | bobbylarrybobby wrote:
       | > In Structure and Interpretation of Computer Programs, Abelson
       | and Sussman describe an arithmetic system in which the arithmetic
       | types form an explicit lattice. Every type comes with a
       | "promotion" function to promote it to a type higher up in the
       | lattice. When values of different types are added, each value is
       | promoted, perhaps repeatedly, until the two values are the same
       | type, which is the lattice join of the two original types. I've
       | never used anything like this and don't know how well it works in
       | practice, but it seems like a plausible approach, one which works
       | the way we usually think about numbers, and understands that it
       | can add a float to a Gaussian integer by construing both of them
       | as complex numbers.
       | 
       | Julia uses this in pretty much all of its math functions and
       | probably elsewhere as well, and it works unbelievably well. The
       | type promotion system makes math Just Work, even (and especially)
       | in the face of different-sized numbers. The result is that 99.9%
       | of the time you simply don't have to think about the types of
       | your numbers. Here are some examples from the docs:
       | julia> promote_type(Int64, Float64)       Float64
       | julia> promote_type(Int32, Int64)       Int64              julia>
       | promote_type(Float32, BigInt)       BigFloat              julia>
       | promote_type(Int16, Float16)       Float16              julia>
       | promote_type(Int64, Float16)       Float16              julia>
       | promote_type(Int8, UInt16)       UInt16
       | 
       | And not only are types promoted, but in well-typed Julia code,
       | the deduction of promotion types happens at compile time instead
       | of runtime, so there is almost no performance cost to this
       | either.
        
       ___________________________________________________________________
       (page generated 2022-04-30 23:00 UTC)