TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Why don't more languages offer flow typing?

192 点作者 fourteenminutes大约 3 年前

24 条评论

jerf大约 3 年前
This is not a complete answer, but covers some languages. If you program too exclusively in dynamically-typed languages, you can be too used to not thinking about how physically large your types are, because you work in a world where <i>everything</i> is boxed, and allocations so plentiful you don&#x27;t even hardly have a way of thinking about them because your language does them at the drop of a hat, and so on.<p>But there are many strongly-typed languages that look at types at compile time and decide how large they are, by which I mean, how many bytes of physical RAM they take. These languages have gotten better and better over time at the APIs they offer that give the benefits of this without incurring the programmer costs, but under the hood, no matter how slick they&#x27;ve gotten, there is still a mapping of type -&gt; size &amp; layout. In these languages, this sort of narrowing is a great deal more complicated, because it implies a new type, which will probably require more allocation. For instance, in the example given between a &quot;string&quot; and a &quot;number&quot;, that definitely looks like two differently-sized types to me. This would become very complicated very quickly as you start having to allocate these things in ways that can&#x27;t be transparent to the user, you have to pass these types around, etc. etc.<p>One would expect that the affordances of statically-typed languages would end up being very different. And in fact, they are. Many modern statically-typed languages can solve the same problems outlined in the post, they just solve it in a different way. In this case, an Either, or more generally, a sum type. Then &quot;getViewsOf&quot; returns a value of that sum type and you can switch on it based on the value. It isn&#x27;t exactly the same, in fact I&#x27;m not sure there&#x27;s one aspect of it that&#x27;s the same let alone the whole, but it solves the same problems.<p>So my suggestion would be that there are in fact a lot of languages that essentially have the same feature. They just don&#x27;t call it &quot;flow typing&quot; and it&#x27;s not spelled the same way.
评论 #30909756 未加载
评论 #30908024 未加载
评论 #30910391 未加载
评论 #30908048 未加载
评论 #30908141 未加载
评论 #30914610 未加载
评论 #30907616 未加载
评论 #30911422 未加载
valenterry大约 3 年前
The answer is simple: because other languages don&#x27;t need it - they have different features to deal with it. The author even mentions it: pattern matching.<p>Just that he picks a language that doesn&#x27;t support union-types. But that doesn&#x27;t mean that flow typing would be necessary here - it means that the language(s) should support union-types and extend their pattern matching accordingly.<p>In fact, I would say that flow typing is almost like a workaround for missing pattern matching.
评论 #30908312 未加载
评论 #30911181 未加载
评论 #30909579 未加载
评论 #30908101 未加载
chriswarbo大约 3 年前
Nobody&#x27;s mentioned dependent typing yet, but languages like Idris and Agda will &quot;narrow types based on control flow&quot; (in fact, since they&#x27;re pure-functional, control-flow is the same as data-flow). For example, zipping two vectors of the same length:<p><pre><code> zip: Vec n t1 -&gt; Vec n t2 -&gt; Vec n (t1, t2) zip Nil Nil = Nil zip (Cons x xs) (Cons y ys) = Cons (x, y) (zip xs ys) </code></pre> Here the &#x27;Vec&#x27; type has two constructors, Nil and Cons. Since their types specify the same length, the type-checker knows we can ignore the &#x27;zip Nil Cons&#x27; or &#x27;zip Cons Nil&#x27; cases.
评论 #30911300 未加载
sfvisser大约 3 年前
Contrary to what many comments here state, I don’t think this is only useful in a dynamic runtime kind-of environment. I often think I could use some form of this in Rust and&#x2F;or Haskell. In a very specific way:<p>I often want a single constructor&#x2F;branch of an enum (sum type) to a be a type as well, specifically a sub-type of the full enum. So once I learn about what branch a value is, I can treat it like that and even first class pass it around – without unpacking.<p>I think this should be implementable in Rust without any runtime overhead as pure syntactic sugar. You get the same effect by creating a new struct for every set of values per branch and using those wrappers instead.
评论 #30911572 未加载
评论 #30910969 未加载
评论 #30911540 未加载
phillipcarter大约 3 年前
It&#x27;s not really called out as such, but the C# 8 feature Nullable Reference Types is an example of deeply embedding flow typing into a language. The nullability state of a reference type can change based on many things you can do in code. It&#x27;s really neat and extremely intricate.
评论 #30911029 未加载
fuzzy2大约 3 年前
This is called Type Narrowing by the way. Control flow analysis is only one way this works in TypeScript.<p>Most languages don’t have the type system necessary to make this work in a sensible way.
评论 #30908258 未加载
ashton314大约 3 年前
See also “Occurrence Typing” in Typed Racket. [^1]<p>&gt; One of Typed Racket’s distinguishing type system features is occurrence typing, which allows the type system to ascribe more precise types based on whether a predicate check succeeds or fails.<p>One of the big advantages is that it lets a text editor use the added information to supply better auto-complete suggestions. It also makes it possible to eliminate some safety checks at runtime because the type checker has ensured that certain calls will be safe based on prior conditional checks.<p>[^1]: <a href="https:&#x2F;&#x2F;docs.racket-lang.org&#x2F;ts-guide&#x2F;occurrence-typing.html" rel="nofollow">https:&#x2F;&#x2F;docs.racket-lang.org&#x2F;ts-guide&#x2F;occurrence-typing.html</a>
endtime大约 3 年前
Dart does this, or at least something very similar, and it is really nice. It&#x27;s most useful for narrowing nullable types to their non-nullable counterparts after a null-check, but the subclass case works too.
评论 #30907666 未加载
评论 #30907711 未加载
colonwqbang大约 3 年前
Doesn&#x27;t &quot;flow typing&quot; seem more like a bandage for the if-statement in languages that have nothing better.<p>There is a language construct which is purpose built for unpacking sum types: The case expression (sometimes called match expression)<p><pre><code> case resp of Views n -&gt; ... Error e -&gt; ... </code></pre> Inside each branch, we have access to a value of the more specific type.
评论 #30909815 未加载
评论 #30913160 未加载
Orangeair大约 3 年前
&gt; Even in the presence of such tests, Java demands that the author cast their objects appropriately.<p>This is one of the many reasons why I&#x27;ve found Kotlin so refreshing after five years of writing enterprise Java<p><a href="https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;typecasts.html#smart-casts" rel="nofollow">https:&#x2F;&#x2F;kotlinlang.org&#x2F;docs&#x2F;typecasts.html#smart-casts</a>
adamddev1大约 3 年前
I looove flow typing &#x2F; type narrowing. HTDP taught how to think through it (data type narrowing &#x2F; checking for and handling different types) while building functions. TypeScript let&#x27;s me <i>enforce</i> the same process with intellisense warning me whenever I get off track. It&#x27;s been a joy to see how quickly and solidly the code stacks up.
john567大约 3 年前
I&#x27;m trying to design a programming language that can be compiled into C99 and it&#x27;s not at all clear to me how you represent these things statically.<p>The idea from a programming point of view is really nice and in a dynamic programming language environment everything is an object and you can pass whatever through any function. This doesn&#x27;t work all that well if you want to translate your program into a statically typed environment, in a cost effective way.<p>You need some way to reason about the type information that hopefully doesn&#x27;t create memory allocations all over the place.<p>This can be done by introducing tagged data types but it can also lead to a combinatorial nightmare where you need to generate a lot of extra code.<p>In general, I don&#x27;t think all this complexity is warranted in a statically typed environment and depending what you are doing this doesn&#x27;t end up being that important. Even if, it&#x27;s a nice to have, it&#x27;s mostly that, a nice to have.
评论 #30908826 未加载
cmrdporcupine大约 3 年前
So I&#x27;ve been working on a personal project which is in a mix of C++ and TypeScript -- partially because of practicality and partially to learn TypeScript, and I&#x27;m really liking the language.<p>One question I have is whether there&#x27;s any possibility that TypeScript could, in the long run, gain performance advantages over pure JS? That the compiler could leave behind some type information artifacts so that V8 (or similar) could use that information at runtime to optimize method dispatch and other operations?<p>Likewise with the flow pieces mentioned here, I gotta think that the VM could optimize out certain runtime checks on dispatch or conditional branching if it knows that the compiler has already checked for these?
评论 #30909262 未加载
评论 #30916293 未加载
评论 #30910475 未加载
mosdl大约 3 年前
fyi Java 16 did add pattern matching for instance of so you can avoid the cast.
评论 #30908012 未加载
joe_fishfish大约 3 年前
Kotlin does this, it&#x27;s great.
cannabis_sam大约 3 年前
This question actually has a simple answer:<p>Most languages have embraced statements over expressions for a lot of language constructs.<p>This causes a lot of issues:<p>An if-statement is in essence a function from boolean to unit&#x2F;() (i.e. from a barely useful type to a type with no useful information), while an if-expressions will contain enough type information to at least provide this kind of “flow typing”, (even if it induces an amount of boolean blindness.)<p>It’s even worse when you get to loop-statements, which often is a function from unit&#x2F;() to (unit&#x2F;() | bottom&#x2F;⊥), compared to a map or a reduce or fold, which again provide enough type information to provide this kind of “flow typing”.<p>Sometimes you will of course need to provide impure conditionals or loops, but that can be made explicit in the type system.<p>IMHO statements in programming languages are an anti-pattern.
评论 #30911808 未加载
评论 #30911163 未加载
baby大约 3 年前
What’s the difference with type inference? Is it that flow typing can happen at runtime for just in time &#x2F;noncompiled languages?
评论 #30909792 未加载
mhh__大约 3 年前
Unless you build a compiler around it it&#x27;s probably quite hard to implement
AtlasBarfed大约 3 年前
... isn&#x27;t this just OOP inheritance&#x2F;subtyping&#x2F;interface implementation with if&#x2F;then checks for the more specific types?
munificent大约 3 年前
I work on a language, Dart, which also relies heavily on flow typing (which it calls &quot;type promotion&quot; because apparently every language needs their own name for it). We use it both for subtype tests and null checks. It&#x27;s really nice and a large net win for Dart especially given its history.<p>However, if I had a time machine and could redesign Dart from scratch, I would be tempted avoid flow typing and instead do something more like Rust and Swift do: Variables keep their static type but have pattern maching and nice syntactic sugar for simple use cases of it to make it easier to bind <i>new</i> variables with the narrowed type.<p>The main problem is that flow typing is very complex, subtle, and can fail in ways that users find surprising. For example:<p><pre><code> foo(Object obj) { closure() { obj = &quot;not int any more.&quot;; } if (obj is int) { print(obj.abs()); } closure(); } </code></pre> This function is technically safe, but it&#x27;s very hard for static analysis to reliably prove what kinds of flow analysis are valid when closures come into play. If that closure can escape, it can be impossible to prove that it won&#x27;t be called before the variable is used.<p>A simpler, more annoying example is:<p><pre><code> class C { Object obj; foo() { if (obj is int) { bar(); print(obj.abs()); } } bar() {} } </code></pre> This code looks like it <i>should</i> be fine. But if C is an unsealed class and some subclass overrides `bar()` to assign to `obj`, then the promotion could fail. Because of this, Dart can&#x27;t promote fields and it causes no end of user annoyance. Top-level variables and static fields have similar limitations.<p>Even when it works, it can be surprising:<p><pre><code> foo(Object obj) { if (obj is int) { var elements = [obj]; } } </code></pre> Should `elements` be inferred as a `List&lt;Object&gt;` or `List&lt;int&gt;`? What about here:<p><pre><code> foo(Object obj) { if (obj is int) { var elements = [obj]; elements.add(&quot;not int&quot;); } } </code></pre> Should that `add()` call be OK or an error?<p>Flow analysis is cool and feels like magic. It does the right thing 90+% of the time, but there&#x27;s a lot going on under the hood that pops up in weird surprising ways sometimes.<p>It&#x27;s probably the right thing to do if your language has already invested in an imperative style. But if you have the luxury of defining a language from scratch, I think you can get something simpler and more predictable if you make it easier to define <i>new</i> variables of the refined type instead of <i>mutating</i> the type of an existing variable.<p>Dart is heavily imperative, so I think flow analysis makes sense for it. Accommodating an imperative style is one of the main things that makes Dart so easy for new users to pick up, and that&#x27;s an invaluable property. But I admit I envy Swift and Rust at times.
ljloisnflwef大约 3 年前
We don&#x27;t hold language authors accountable, and accept mediocrity.
throwaway81523大约 3 年前
Is this something like GADT&#x27;s?
评论 #30910715 未加载
kragen大约 3 年前
Hafiz writes:<p>&gt; <i>almost every enterprise Java codebase I&#x27;ve had to work with has observed this pattern of testing whether an object is an instance of particular subclass, and using that to drive further computation. Even in the presence of such tests, Java demands that the author cast their objects appropriately. Of course you can always introduce an intermediate variable after the test, but I argue that this is still too much—upon verifying the instance of an object, the type system should be smart enough to update the object&#x27;s type appropriately!</i><p>Because Java considers null to inhabit every type, in a Java project a few years ago, I handled this in a dynamic_cast-like way, as follows (abbreviated):<p><pre><code> public abstract class Security { &#x2F;&#x2F; ... public Stock asStock() { return null; } public Future asFuture() { return null; } } public class Stock extends Security { &#x2F;&#x2F; ... public Stock asStock() { return this; } } public class Future extends Security { public final SecurityTable factory; public final String exchange, symbol; &#x2F;&#x2F; ... public Future asFuture() { return this; } } </code></pre> This allows you to write relatively uncluttered type-dispatching downcasting code like this:<p><pre><code> public boolean contains(Security sec) { Future f = sec.asFuture(); return f != null &amp;&amp; SecurityTable.this == f.factory &amp;&amp; f.symbol.equals(symbol); } </code></pre> Of course this violates the open-closed principle (the abstract base class should be closed for modification) and official OO doctrine is that if you want different behavior for different subclasses you should put that behavior into a method that gets overridden by the subclasses, not in a conditional that attempts a downcast, so we did that a lot more often. But I found it a pleasant solution to the problem in the context of this Java project.<p>Of course it doesn&#x27;t help in languages without implicit nullability, like TypeScript, which would ideally be all statically-typed languages.<p>— ⁂ —<p>The big difficulty with flow typing is, as I see it, not that it clashes with nominal typing; it&#x27;s that it incorporates your <i>compiler&#x27;s</i> static control flow analysis capabilities into your <i>language&#x27;s</i> type system. Consider Hafiz&#x27;s Java example:<p><pre><code> if (node instanceof DomNode.Element) { Layout layout = ((DomNode.Element) node).layout; return new RenderNode.Styled(layout, &#x2F;* ... *&#x2F;); } return new RenderNode.Noop(&#x2F;* ... *&#x2F;); </code></pre> It is entirely reasonable to request that the type system handle this and allow you to write:<p><pre><code> if (node instanceof DomNode.Element) { Layout layout = node.layout; return new RenderNode.Styled(layout, &#x2F;* ... *&#x2F;); } return new RenderNode.Noop(&#x2F;* ... *&#x2F;); </code></pre> But how about this? Now we&#x27;re no longer inside the static extent of the `if`, and we&#x27;re depending on the compiler to recognize the unconditional early return:<p><pre><code> if (!(node instanceof DomNode.Element)) { return new RenderNode.Noop(&#x2F;* ... *&#x2F;); } Layout layout = node.layout; return new RenderNode.Styled(layout, &#x2F;* ... *&#x2F;); </code></pre> How about constant folding and partial evaluation?<p><pre><code> if (1 == 0 || node instanceof DomNode.Element) { Layout layout = node.layout; return new RenderNode.Styled(layout, &#x2F;* ... *&#x2F;); } return new RenderNode.Noop(&#x2F;* ... *&#x2F;); </code></pre> Do we want to skip type checking entirely for dead code? Straightforwardly flow typing gives us that the type of every variable inside unreachable code is void, the uninhabited type, so any operation whatsoever on it is type-valid. Do we want the compiler to accept code like this?<p><pre><code> if (1 == 0) { Layout layout = node.layout + node &#x2F; node; return new RenderNode.Styled(layout, &#x2F;* ... *&#x2F;); } return new RenderNode.Noop(&#x2F;* ... *&#x2F;); </code></pre> And of course doing control-flow analysis precisely isn&#x27;t feasible due to the halting problem; you need to do some conservative approximation.<p>So, if you want your programs to be portable from one version of the compiler to the next, somewhere you need to write down precisely <i>what</i> conservative approximation you&#x27;re using for control-flow analysis, and in particular what you&#x27;re <i>not</i>.
评论 #30913292 未加载
irrational大约 3 年前
I forgot about flow typing. Is that still an option on iOS?
评论 #30907476 未加载