TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

What was wrong with SML?

127 pointsby rdpintqogeogsaaabout 3 years ago

18 comments

jasonhanselabout 3 years ago
IMHO Haskell&#x27;s lazy evaluation has some significant disadvantages compared to SML&#x27;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.<p>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&#x27;s callers. In effect, the performance of a function is no longer just determined by that function&#x27;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.<p>Granted, any optimizing compiler can have this effect, but it&#x27;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.
评论 #31220309 未加载
eatonphilabout 3 years ago
For folks interested in learning Standard ML, check out &#x2F;r&#x2F;sml [0] and the sticky post [1] with a getting started guide.<p>[0] <a href="http:&#x2F;&#x2F;reddit.com&#x2F;r&#x2F;sml" rel="nofollow">http:&#x2F;&#x2F;reddit.com&#x2F;r&#x2F;sml</a><p>[1] <a href="https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;sml&#x2F;comments&#x2F;qyy2gs&#x2F;getting_started_with_standard_ml&#x2F;" rel="nofollow">https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;sml&#x2F;comments&#x2F;qyy2gs&#x2F;getting_started...</a>
bobbylarrybobbyabout 3 years ago
&gt; 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&#x27;ve never used anything like this and don&#x27;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.<p>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&#x27;t have to think about the types of your numbers. Here are some examples from the docs:<p><pre><code> julia&gt; promote_type(Int64, Float64) Float64 julia&gt; promote_type(Int32, Int64) Int64 julia&gt; promote_type(Float32, BigInt) BigFloat julia&gt; promote_type(Int16, Float16) Float16 julia&gt; promote_type(Int64, Float16) Float16 julia&gt; promote_type(Int8, UInt16) UInt16 </code></pre> 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.
评论 #31220336 未加载
评论 #31222154 未加载
eatonphilabout 3 years ago
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&#x27;ve written recently [0] about how active it surprisingly is.<p>I also disagree that its failure to &quot;succeed&quot; has anything to do with syntax or semantics and solely that it doesn&#x27;t have a Jane Street or any company publicly behind it.<p>[0] <a href="https:&#x2F;&#x2F;notes.eatonphil.com&#x2F;standard-ml-in-2020.html" rel="nofollow">https:&#x2F;&#x2F;notes.eatonphil.com&#x2F;standard-ml-in-2020.html</a>
评论 #31218867 未加载
评论 #31224673 未加载
评论 #31218783 未加载
tialaramexabout 3 years ago
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 <i>despite</i> the fact that most graduates don&#x27;t end up using SML to write anything.<p>In the course of my education I experienced some things which I&#x27;m convinced are a <i>bad idea</i> even though they worked out OK for me such as selective education (whole schools only for &quot;talented&quot; children) and single sex secondary education, but SML as First Language is not one of those things. It worked well for me <i>and</i> I&#x27;m convinced it&#x27;s a good idea even though I would not advocate writing new real world projects in SML unless you&#x27;ve got some very particular reason.
评论 #31234192 未加载
评论 #31221197 未加载
porcodaabout 3 years ago
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.<p>CakeML is also a very cool project in SML land.
评论 #31222586 未加载
评论 #31220740 未加载
bzxcvbnabout 3 years ago
I&#x27;m not really convinced by the author&#x27;s first example. While an element of type bool is an instance of type a, an element of type bool -&gt; bool is <i>not</i> an instance of type a -&gt; a.<p>The issue is precisely an issue of variance, which is mentioned in reference to Scala, but somehow it&#x27;s glossed over. The type a -&gt; a is covariant in its second argument, but contravariant in its first argument. As a result, you cannot &quot;specialize&quot; it to bool -&gt; bool, because specialization is really another name for covariance.<p>Another name for contravariance is &quot;generalization&quot;. If you ask for something of type &quot;bool -&gt; b&quot;, and someone provides you with some function f of type &quot;a -&gt; b&quot;, then you&#x27;re happy. The map f is indeed an instance of &quot;bool -&gt; b&quot;: it can eat anything, so it can eat booleans. But with type a -&gt; a, you cannot do what I just did: type &quot;a&quot; is already the most general one, and you cannot do better.<p>If SML accepted something of type &quot;bool -&gt; bool&quot; for an instance of type &quot;a -&gt; a&quot;, then it was a fundamental error. But this doesn&#x27;t mean that the whole thing should have been thrown out and replaced with monads. In fact, I don&#x27;t really get how monads have anything to do with the problem at hand. If Haskell can prevent the following code, then I don&#x27;t see why SML couldn&#x27;t have prevented the authors&#x27; code:<p><pre><code> do m &lt;- STRef id m &lt;- not m 42</code></pre>
评论 #31220813 未加载
评论 #31222284 未加载
评论 #31221514 未加载
评论 #31221928 未加载
评论 #31239878 未加载
shpongledabout 3 years ago
SML is one of my favorite languages (I&#x27;ve been (very) slowly writing a compiler &amp; language server for it).<p>Sure, it has some warts&#x2F;differences compared to newer languages - we have moved towards traits&#x2F;typeclasses&#x2F;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&#x27;s extremely simple, yet still powerful and expressive. I hope we will see continued work on SML&#x2F;Successor ML descendants (like 1ML, etc), because I think there&#x27;s still potential there<p>I think some updated language tooling would dramatically help.
评论 #31219721 未加载
Athasabout 3 years ago
&gt; Haskell&#x27;s primary solution to this is to burn it all to the ground. Mutation doesn&#x27;t cause any type problems because there isn&#x27;t any.<p>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&#x27;t just break referential transparency; it can also fundamentally break memory safety. Now, you may say that unsafePerformIO is obviously unsafe (it&#x27;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&#x27;t just get a function that doesn&#x27;t do what you expected - you may have subverted memory safety entirely.<p>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.
fanf2about 3 years ago
There was a history of Standard ML published as part of HOPL4 a couple of years ago - <a href="https:&#x2F;&#x2F;dl.acm.org&#x2F;doi&#x2F;10.1145&#x2F;3386336" rel="nofollow">https:&#x2F;&#x2F;dl.acm.org&#x2F;doi&#x2F;10.1145&#x2F;3386336</a> I was particularly interested by the more recent history, after SML &#x27;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.)<p>Despite that there is&#x2F;was an SML Basis Library project, and a Successor ML project, but it looks like even the die hard fans have drifted away <a href="https:&#x2F;&#x2F;smlfamily.github.io&#x2F;" rel="nofollow">https:&#x2F;&#x2F;smlfamily.github.io&#x2F;</a>
pjmlpabout 3 years ago
What was wrong is not having a killer application or an industry giant pushing it.<p>Grammar and language semantic are details regarding language adoption.
评论 #31218713 未加载
wrpabout 3 years ago
How about OCaml? I studied it alongside SML and liked it a bit better. Whenever something in SML struck me as a poor design choice, I looked over and saw that Caml had done something about it. (I felt the &quot;O&quot; was a completely unnecessary addition.)
chombierabout 3 years ago
&gt; Scala has a very different solution to this problem, called covariant and contravariant traits.<p>I thought Scala had an even stricter value restriction than ML, where only function&#x2F;methods may get a polymorphic type?
评论 #31221231 未加载
lexi-lambdaabout 3 years ago
I don’t think the complaints about evaluation order in this blog post really make sense. The evaluation order of `map` in SML is no more mysterious than the evaluation order of `mapM` in Haskell. The use of explicit monadic sequencing has its advantages (as well as nontrivial disadvantages), but this is not one of them. This is particularly true if `mapM` is written using applicative functors, as the definition<p><pre><code> mapM :: Monad m =&gt; (a -&gt; m b) -&gt; [a] -&gt; m [b] mapM f [] = return [] mapM f (x:xs) = (:) &lt;$&gt; f x &lt;*&gt; mapM f xs </code></pre> is virtually identical in structure to the SML definition<p><pre><code> fun map f [] = [] | map f (x :: xs) = op:: (f x) (map f xs) </code></pre> aside from the “plumbing” of `return`, `&lt;$&gt;`, and `&lt;*&gt;`. Indeed, the whole motivation of applicative functors, as well as the source of their name, was a desire to write code in a form closer to an <i>applicative style</i>, which is to say non-monadic, direct-style code like the SML example. The blog post says<p>&gt; Does it print the values in forward or reverse order? One could implement it either way.<p>but obviously this is also true of `mapM`. That would just be a different function. Monadic sequencing does not help with this at all.<p>Furthermore, the author mentions algebraic effect systems. It isn’t clear to me from the wording if the intent is to offer them as a solution for the shortcomings of monadic encodings or as a nicer way to pin down evaluation order, but the latter is certainly not true—the two are entirely orthogonal. Algebraic effect systems depend on the evaluation order being well-defined by other means to work in the first place. In fact, one could argue that the entire point of algebraic effect systems is to allow the composition of different effects while <i>respecting</i> an underlying notion of evaluation order.
throwamonabout 3 years ago
A bit off-topic, but could someone ELI5 what a lattice is in this context?
评论 #31218823 未加载
评论 #31219658 未加载
评论 #31218649 未加载
kragenabout 3 years ago
I think that in some sense the <i>correct</i> solution to the reference type problem is the Scala solution, which depends on subtyping. This is pretty tricky to reason through, and adding subtyping makes type inference a lot more difficult, so it&#x27;s hardly surprising that SML avoided this, but it provides a much more satisfying solution. (OCaml later embraced subtyping, but in the particular case of mutable references, it instead adopted the same value-restriction approach as SML.)<p>Here&#x27;s the way I understand it; be warned that I&#x27;m just starting to understand this stuff, so I might have got something wrong. I&#x27;d welcome corrections.<p>S is a subtype of T (S &lt;: T) iff you can use an object of type S wherever you need an object of type T. This gives us a partial order on types, in the sense that<p><pre><code> ∀T: T &lt;: T ∀S:∀T: S &lt;: T ∧ T &lt;: S ⇒ S = T ∀S:∀T:∀U: S &lt;: T ∧ T &lt;: U ⇒ S &lt;: U </code></pre> which all follow from the above informal definition. Sometimes we make it a lattice, for example by adding a ⊤ type (&quot;top&quot;) that everything is a subtype of and a ⊥ type (&quot;bottom&quot;) that is a subtype of everything.<p>In general in the presence of subtyping we can only infer type <i>bounds</i> on most things, not exact types. For example, consider that {3} &lt;: ℤ &lt;: ℝ &lt;: ℂ; if we see a function being applied to the value 3, we can infer that it must be at least applicable to {3}, but a function that is applicable to ℤ or ℝ would also work.<p>Given this informal definition, surprisingly, α → α (the type of the identity function) is a &quot;subtype&quot; of bool → bool, not vice versa. That is, α → α &lt;: bool → bool. Whenever we want a function of type bool → bool, we can use a function of type α → α, but not vice versa.<p>α → α is a weird type because it includes an implicit universal quantifier: ∀α: α → α. As it turns out, the judgment above that α → α &lt;: bool → bool is not because α &lt;: bool or bool &lt;: α. It&#x27;s because bool → bool is what you get when you instantiate the quantifier with a particular value, α = bool.<p>There&#x27;s <i>also</i> a function-specific rule for subtyping, and it&#x27;s a real mindbender: B → C &lt;: A → D if A &lt;: B and C &lt;: D. We say functions&#x27; argument types are <i>contravariant</i> and their results are <i>covariant</i>. Considering the A &lt;: B case, if we need a function from integers to booleans, we can use a function from real numbers to booleans—we just won&#x27;t happen to invoke it with any non-integer real numbers, but it will still work, assuming there aren&#x27;t incompatible binary representations at play (as in the case of the OCaml +. and + operators MJD mentions.). Considering the C &lt;: D case, we can also use a function from integers to true: it will never return a value we cannot use as a boolean.<p>We can decompose the operation of taking a reference to x, ref x, into a step of creating a reference r and then applying the reseating operation r := x to it. This operation is valid iff the assignee is of a subtype of the referent type; that is, (r : T ref) := (x : S) is valid iff S &lt;: T. That&#x27;s because later when we read r we will be using its value as a T, so any subtype of T is fine. So for ref (fn x ⇒ x), we have that S = α → α, so we can derive the type bound that T must be some <i>supertype</i> of α → α. As mentioned above, this includes bool → bool, but it also includes ℤ → ℤ, (ℤ × ℂ) → (ℤ × ℂ), and the polymorphic type α → α itself. So reseating a reference is contravariant: we can write a value we know is an integer <i>or anything more specific</i> (a subtype such as a positive integer) into a reference we know to hold an integer <i>or anything more general</i> (a supertype such as a real number).<p>The dereferencing operation !r turns out to instead be covariant: !(r: S ref): T is valid iff S &lt;: T. That is, we&#x27;re going to use the result as type T, so the reference can hold any subtype of T. So if we see ((!m) 23) we have (!m) : {23} → ⊤, from which we can deduce a type bound (m : U) where U &lt;: ({23} → ⊤) ref. That is, the type of the referent of m is a subtype of a &quot;function that can take 23 and return something&quot;. For example, (ℤ → string) &lt;: ({23} → ⊤), because {23} &lt;: ℤ and functions are contravariant in their arguments, and string &lt;: ⊤ (because every type is a subtype of ⊤), so if m has type (ℤ → string) ref, we&#x27;re good. (bool → bool) ref would not work, because (bool → bool) ref is not a subtype of ({23} → ⊤) ref, because bool → bool is not a subtype of {23} → ⊤, because, even though bool &lt;: ⊤, {23} is not a subtype of bool.<p>How does this resolve the problem? If m is of type (α → α) ref, ((!m) 23) <i>would</i> work; for example, if m happens to be a reference to the identity function, it evaluates to 23. (In many languages that&#x27;s the only function of type α → α, in the interests of making the Curry–Howard correspondence meaningful.) But if we&#x27;ve previously seen (m := not), we have an incompatible type bound: (not : bool → bool), so we know that m is of type (bool → bool) ref or a ref to <i>some supertype of</i> bool → bool, U ref where bool → bool &lt;: U. And, as I said above, it is <i>not</i> the case that bool → bool &lt;: α → α; it&#x27;s the other way around.<p>This seems to give us a nice, clean solution to the problem of types for references. Aside from being confusing as hell, the cost is that instead of inferring a <i>type</i> for every expression we can now only infer an <i>infinite set of possibilities</i> for its type. This sounds ridiculous but it is precisely what OCaml does for polymorphic variants and objects (though not mutable refs; details are in <a href="https:&#x2F;&#x2F;v2.ocaml.org&#x2F;manual&#x2F;polymorphism.html" rel="nofollow">https:&#x2F;&#x2F;v2.ocaml.org&#x2F;manual&#x2F;polymorphism.html</a>). With polymorphic variants:<p><pre><code> # let f a = match a with 1 -&gt; `P (3, 4) | _ -&gt; `Q (5) ;; val f : int -&gt; [&gt; `P of int * int | `Q of int ] = &lt;fun&gt; # let g b = match b with `P (c, d) -&gt; c + d | `Q (c) -&gt; c ;; val g : [&lt; `P of int * int | `Q of int ] -&gt; int = &lt;fun&gt; </code></pre> The &quot;&lt;&quot; here means that what&#x27;s being given is an &quot;upper bound&quot;: it&#x27;s perfectly acceptable to apply g to an expression whose type is a <i>subtype</i> of the type given, like this:<p><pre><code> # `Q 2 ;; - : [&gt; `Q of int ] = `Q 2 # g (`Q 2) ;; - : int = 2 </code></pre> But the &quot;&gt;&quot; on f&#x27;s return type is a &quot;lower bound&quot;: you can only use f in a context where its return type <i>is a subtype of</i> the expected type. Including the same type, so we can pass it to g:<p><pre><code> # g (f 1);; - : int = 7 # g (f 2);; - : int = 5 </code></pre> Similarly we can write a function that calls a couple of methods on its arguments:<p><pre><code> # let mf o = (o#foo ; o#bar + 1) ;; val mf : &lt; bar : int; foo : &#x27;a; .. &gt; -&gt; int = &lt;fun&gt; </code></pre> Here OCaml infers that the object needs to have a method &quot;bar&quot; returning int and a method &quot;foo&quot; of some type, but the .. makes this an upper-bound object type: it&#x27;s okay for the object to be of some subtype that has more methods. And similarly if you construct an object with some methods, it can be used in a context that requires some supertype that doesn&#x27;t have all of them. (There&#x27;s a very close analogy between an object with a certain set of methods and a function that can be applied to terms with a certain finite set of polymorphic variant tags.)<p>The subtyping approach would seem to provide a logical solution to the problem of mutability, but it isn&#x27;t the approach OCaml took, for reasons I don&#x27;t understand. It&#x27;s confusing and difficult to understand, but less so than OCaml&#x27;s current set of rules. I suspect the answer is that OCaml had to be backward-compatible with earlier versions of Caml before subtyping was added, but possibly it&#x27;s instead to keep the type inference problem decidable or something.
adultSwimabout 3 years ago
What was wrong with SML?<p>It didn&#x27;t have a big company like Sun pushing it
GnarfGnarfabout 3 years ago
&quot;SML&quot; = Standard Meta Language. Talk about <i>betriebsblind</i>.