[HN Gopher] Ante: A low-level functional language ___________________________________________________________________ Ante: A low-level functional language Author : cheesestain Score : 332 points Date : 2022-06-17 07:39 UTC (15 hours ago) (HTM) web link (antelang.org) (TXT) w3m dump (antelang.org) | ynoxinul wrote: | Looks interesting. How are closures implemented? Is it possible | to use Ante without heap? Are function arguments passed by value? | jfecher wrote: | Hello, author here! Closures are implemented with the | environment parameter as an extra parameter on a function type. | So internally a function `i32 - i32 -> i32` (wonky function | type syntax currently with - separating arguments) which uses | an environment of type String is represented as a pair of the | function and its environment: `(i32 - i32 - String -> i32), | String`. The same way C++ and Rust represent their closures. | | Function arguments are passed by value currently, though I may | explore pass by move and other options in the future. | | I hope for ante to be usable without a heap, though the `ref` | type automatically handling lifetimes makes this more | difficult. These refs compile to an equivalent of destination- | passing in C, but can require dynamic allocation if a function | creates a ref and another function calls the first in a loop. I | also plan on having an Allocate effect for whenever a function | can allocate, so that it is trivial to handle this with your | own handle that can do anything. This would be an easier | alternative to zig's approach of "pass the allocator everywhere | manually," but there are still things I need to iron out, like | how it interacts with the lifetime inference issues above, so | its still in the design phase. | Syzygies wrote: | I avidly follow descendants of Scheme and Haskell (I figured out | for example how to build Idris native on an M1 Mac), and I plan | to follow Ante. I just spent $5k of lunch money on an M1 Ultra | Mac Studio, to use as a math compute server. How do I keep it | busy? Haskell. My "Hello world" parallel benchmark, using reverse | search to enumerate the 66,960,965,307 atomic lattices on six | atoms, took 14 hours in 2009. It now takes four minutes. | | I'd love to start again with a new, small language. I love | Haskell, but it's decades old, and one struggles to work with it | without being torn apart by the gravitational tides of the black | hole at the center of its galaxy. All jokes aside, Haskell really | does reward a PhD in category theory, or the self-taught | equivalent later. | | Functional languages make parallelism easier, and Haskell has put | particular effort into this: Adding a dozen lines to a program | can keep the 16 performance cores of my Mac Studio running full | tilt. I have yet to read any account of how someone else's | favorite language makes parallelism easy, that is aware of the | comparison with Haskell. If I thought there was a reasonable | alternative, I'd jump at trying it. | | Rust appeals because it replaces GC with modern control of | allocation lifetimes. I love how Ante will also explore this. I | use cyclic data structures; how one handles this functionally, | and how one handles this using reference counting, is exactly the | same problem. | | Parallelism should be the first consideration, designing any new | language in 2022. Parallelism is _the_ reason I keep using | Haskell, despite newer alternatives. Any language that runs on a | single core is a toy. This is a plea! | JoshCole wrote: | Also have a computer with a ridiculous number of cores (32) | that I want to keep busy. | | I've found Clojure to dominate the ease of parallelism. For | problems that are actually stupidly parallel the number of | changes to your code is often measured in letters rather than | lines. For example, you might change map to pmap or you might | change reduce to reducers/reduce and change nothing else but | now be fully leveraging all your cores. For problems that | aren't stupidly parallel I feel like Clojure shines even more. | Most languages didn't implement software transactional memory | and encourage it as the default way to work with things that | vary over time, but Clojure did. On account of that you can | have a not massively parallel algorithm that would be full of | tricky lock code in another language and still end up | leveraging all your cores by doing something as simple as map | or pmap, but not run into horrible issues. | thurn wrote: | I'm confused how data structures work in this language, and | there's no documentation about it as far as I can tell. Is this | going to be a Rust-style vectors+iterators system? It it going to | use purely-functional concatenative structures? The first example | you show invokes `map` and `sum` over an Array, but what is this | code actually doing? Making a copy of the data in the array? | Creating a mapping iterator? | jfecher wrote: | Rust-style with Vecs+Iterators is the current style, yes. So | the map+sum example creates an iterator rather than copying the | array. | | I'd like to remove iterators in favor of Generators | (implemented with Algebraic effects) in the future though since | they're much easier to define. Actually switching over is on | hold until effects are implemented and speed tests are done to | ensure they're compareable speed to iterators (which they | should hopefully be due to monomorphisation of effects + | handlers). | alilleybrinker wrote: | If anyone is interested in other programming languages also | written in Rust, there's a community-maintained list! | https://github.com/alilleybrinker/langs-in-rust | dthul wrote: | Looks very cool! Can somebody enlighten me what's happening in | the Algebraic Effects example? Specifically this part: | handle f () | flip () -> (resume true + resume false) / | 2.0 | | Does `handle f ()` call `calculation` and the `| ...` part | "injects" the `flip` effect? I am also quite confused by the part | following `| flip ()`. It somehow returns true or false with a | probability of 50%? And why does this give you the expected value | of the whole calculation function in the end? | yakubin wrote: | Explanation based on my familiarity with Koka: | | This expression matches on the effect _flip()_ and handles it | with the code on the right hand side of _- >_ which becomes the | value of the function invoking the effect. In this case the | handler runs the continuation for both possible values ( | _resume true_ and _resume false_ , i.e. the function was called | once, but it will return twice, with _true_ and _false_ | substituted in the place of _flip()_ in the place which used | this effect) and returns their mean as the average. | | I.e. each time _calculation_ calls _flip()_ , the effect | handler will continue the function for both _true_ and _false_ | and then take the mean of those two results. Comments explain | that _flip()_ should simulate a coin flip, which generally | gives equal probability (1 /2) to _true_ and _false_. Each | _flip()_ in _calculation_ was simulated with equal number of | _true_ and _false_ values, so it fits the expected | distribution. Each time taking the mean of those values is just | an expansion of the integral calculating the expected value, as | per definition of expected value. | | Note that there is some recursion here. While the _flip()_ | handler is executing for the first invokation of _flip()_ , | another instance of the handler will execute for second and | third invokations (inside _resume true_ ). I.e. it calculates | the expected value by taking the mean of the expected values of | each branch created by a flip. When a branch doesn't have any | more flips inside, its expected value is taken to be the | constant value that this branch returns. | jfecher wrote: | Author here, a good way to understand algebraic effects is as | "resumeable exceptions." In this case the expected_value | handler says to run `f ()` and whenever that computation | "throws" a `flip ()` effect to handle it by resuming the | computation with the value true returned for flip. The | continuation continues as normal, subsequent uses of flip are | also handled until the computation finishs. Then we evaluate | the rest of `+ resume false) / 2.0` and resume the computation | (a second time!) in the same place, this time with the value | false. Finally, we add both values returned and divide by two | to get the expected value. | | This way of computing expected value works because for each | flip we essentially have a case-split: the value can be true or | false with a 50-50 chance, so the expected value of the whole | thing is is 0.5 * the result of the true branch + 0.5 * the | result of the false branch! | | This is more of a fun use of effects than a practical one. If | you're still curious about effects, check out the full page on | them here: https://antelang.org/docs/language/#algebraic- | effects which includes some actually useful effects like State, | Generators, looping constructs, parsers, etc. | dthul wrote: | Thanks for the explanation, I will definitely check out the | docs on algebraic effects! | jkarni wrote: | I don't fully understand it yet, but I think it resumes twice, | adds the results, and divides by two. | | the `| flip ()` is pattern matching on that effect term, I | believe. So essentially: when you see `flip ()` inside anything | in the argument of `expected_value`, capture the continuation | there, run that argument once with `true` and once with | `false`, add the results, divide by two. | HelloNurse wrote: | How does string interpolation work? In what context, exactly, are | the placeholders checked and/or evaluated? How are missing or | incompatible placeholder values handled? The semantics aren't | obvious (for instance, how do you deal with string inputs, which | would contain placeholders that cannot be checked in advance?). | jfecher wrote: | String interpolation works by expanding to the concatenation of | several strings. So a string like "the ${foo}." is expanded to | "the " ++ foo ++ ".". There are some things I'd like to change | about the current design. Namely it should probably defer to a | StringBuilder of sorts, and it should possibly do it lazily so | that interpolation can be used in log calls without worry of | whether logging is enabled. | | These are all checked at compile-time though, since | interpolation can only be done inside string literals we can | check for all uses of ${expr} inside literals and ensure e.g. | that expr is convertable to a string. Since these are only in | string literals, all placeholders are known in advance so there | are none that cannot be checked at compile-time. (A string like | "foo \${" ++ "foo}" must both escape the interpolation begin ${ | and is concatenated to the actual string "foo ${foo}" with no | interpolation. Otherwise it would be very confusing having | interpolation happening in unintended circumstances (and would | make string operations less efficient by having to check for | this)). | HelloNurse wrote: | So it's purely syntactic sugar around concatenating plain | string literals and various expressions, it has no relation | to print or other I/O, and in case a string comes from input | (BTW, I don't see any mention of serious IO besides print for | "logging") or from some computation it isn't subject to | interpolation. | | I don't think limiting string interpolation to enhanced | string literals in code (leaving out generic strings) in | order to allow static checking is a practically acceptable | restriction. For example, logging frameworks meant for long- | running application tend to specify message templates in | configuration files or databases, possibly hot-reloading them | at runtime. | jfecher wrote: | More or less, yes. I may later change it to some type like | `Interpolated env` to represent a lazily interpreted string | that interpolates the types in env. So "${1u8} and ${2}" | would be typed as Interpolated (u8, i32). | | I'd like to keep this orthogonal to IO or other specific | use cases so that it is applicable to any use case users | may have rather than tailored to existing ones. You're also | correct there is no real logging framework or anything of | the kind except print really. Ante is still in a quite | early state and there aren't really any substantial | libraries to speak of. As-is, interpolation is really just | some nice sugar to make some common operations more | ergonomic, like f-strings in python. | | I think applications that need their own dynamic | interpolation requirements should define their own | interpolation to handle their specific needs. It would be | unacceptable from a performance and predictability | standpoint for example to support the example of loading | files into a string and have them interpolated at runtime | automatically for all file loads. | markoutso wrote: | How is this low level exactly? | roelschroeven wrote: | It's not, or only partially. "To try to bridge the gap between | high and low level languages, ante adapts a high-level approach | by default, maintaining the ability to drop into low-level code | when needed." says the website. | jfecher wrote: | Author here, to me the lack of a pervasive tracing GC and | values not being boxed by default are important for low level | languages. That and maintaining the ability to drop down and | use low level constructs like raw pointers for optimization | or primitives for new abstractions are essential. | maze-le wrote: | Pointers and manual memory management apparently. | whoomp12342 wrote: | low is relative :-D my thought exactly. | | IMO the only low level language is assembly. Everything else is | some form of abstraction. C/C++ and the likes I tend to call | lower, since in 2022 it is closer to the hardware, and then | sugar languages like python, c#, js, and the likes I call high | level. | wtetzner wrote: | Even assembly languages are abstractions. | shirogane86x wrote: | quoting the github readme: | | > In general, ante is low-level (no GC, values aren't boxed by | default) while also trying to be as readable as possible by | encouraging high-level approaches that can be optimized with | low-level details later on. | whoomp12342 wrote: | with no GC, is there compile time segfault checking like | rust? | jfecher wrote: | Author here, that is the purpose lifetime inference is | meant to serve. It automatically extends lifetimes of | `ref`s so that they are long enough. A key advantage of | these is avoiding lifetime annotations in code, at the cost | of some lack of control since the lifetime is increasingly | handled for you. You can also opt out by using raw pointer | types though and easily segfault with those. | qwertox wrote: | I'm left wondering if the CSV-example considers commas inside a | string field which is surrounded by quotes. | | The other examples are way too confusing for me, except the first | one. | jfecher wrote: | It does not! It was meant to be a rather simple example showing | what using iterators look like. Perhaps interesting to note | that I'll likely be removing iterators in favor of generators | which are easier to use. With monomorphisation of effects it is | my hope that they'll be just as efficient, but Iterators will | remain until tests prove that is the case. | | As for the examples, they definitely showcase things that | aren't present in most languages and can thus be confusing. | That is on purpose though, since ante for me is a language to | experiment with novel features I find interesting. The second | example on lifetime inference for example can be thought of as | like rust's lifetimes but completely inferred and instead of | issuing errors for "x does not live long enough" it will | instead automatically extend the lifetime of x. So my hope is | it is easier to use at the loss of some control (control can be | regained by using other pointer types like `Ptr a` for a raw | pointer used to implement other things on top of). | DeathArrow wrote: | Very interesting stuff! I didn't even thought is possible to | implement a low level functional languages, I always thought a | functional language requires a ton of abstractions. | chriswarbo wrote: | It's certainly possible, although certain styles are more | appropriate than others. | | For example, ATS is ostensibly an ML-like language; but its | "low level" examples look more like "C with stricter types" | (e.g. its Cairo examples) | | https://en.wikipedia.org/wiki/ATS_(programming_language) | pjmlp wrote: | Not at all, there only needs to exist enough low level | primitives (aka compiler intrisics) to build the whole stack. | | That is how Lisp Machines and Xerox PARC workstation OSes used | to be built, or Mirage OS for a more recent example. | | And I do consider Lisp functional, since when I reached | university, the options were Lisp dialects, Mirada was fresh, | Standard ML and Caml Light were rather new. | | There was yet to appear the mentality that OCaml / Haskell === | FP. | dgb23 wrote: | Aside from Lisps being yet again an example of "Simpsons | already did it.", there are also other examples and projects | that go roughly into this direction. Like Roc[0]. Tim | Sweeney[1] seems to be working on integrating functional | programming into games development, which is highly competitive | and very performance sensitive. John Carmack also has been | advocating for using functional programming techniques in game | development (partially). | | Generally I think FP has a bit of a misguided bad rep when it | comes to performance. What makes code slow is indirection that | a compiler cannot reason about and work that doesn't need to be | done. There are FP techniques that can help a compiler or CPU | to generate/transform efficient code and ones that can confuse | them so to speak. Needless mutation can sometimes lead to bad | code as well (from a compiler or CPU perspective), because it | solidifies control. Functional programming is fundamentally a | constraint, computers, interpreters and so on like constraints, | because they can then go about shuffling around stuff without | breaking those constraints if that makes sense. | | [0] https://www.roc-lang.org/ | | [1] https://en.wikipedia.org/wiki/Tim_Sweeney_(game_developer) | epolanski wrote: | At the end of the day what prevents functional languages from | being a good fit for low-level programming is that effective | low-level programming cannot be referentially transparent. | | Might be my 2 cents, but i think Rust can hit a very sweet spot | for functionally-leaning low-level effective programming. | actionfromafar wrote: | I can't see why not - in normal computing, there is I/O. On | embedded it also just "I/O" but a little more brutal than | streams. Still pretty much the same though, at some point you | must reference the outside world. | | What am I missing? | chriswarbo wrote: | > At the end of the day what prevents functional languages | from being a good fit for low-level programming is that | effective low-level programming cannot be referentially | transparent. | | I'm not sure I agree. "Straightforward" FP, e.g. a bunch of | functions defining local vars and calling other functions, | can be pretty hard to make low-level (it pretty much assumes | a GC, and indirected closures; although tech like linear | types can help). | | However, the more abstract, point-free, pattern-heavy FP | seems like a reasonable fit (e.g. pipes/conduit) | throwaway17_17 wrote: | I disagree, the reason almost all 'functional' programming | can not be said to be referentially transparent is because of | the level of abstract the designers take as a base. The work | Greg Morrisette did (which include Robert Harper for some | branches) on Typed and Dependently-Typed Assembly could be | used as the basis for a 'C-level' programming language that | is 'functional'. | | At a slightly higher level, if memory is explicit, and that | would include input and output buffers and the like, then it | is possible to make functions exist inside a 'memory monad', | similar to Haskell's state monad. Then any and all operations | involving the memory used can be made referentially | transparent. | | Now, the real question is would anyone want to program in | such a style? I know I wouldn't mind, it's while I broke down | and designed a personal programming language for my daily | use. But it's a matter of taste and tolerance. | deltaonefour wrote: | Functional is always at odds with low level programming because | of heap allocation. You can't control it because of immutability. | Just a simple map operation does a heap allocation. How does Ante | avoid this problem and give the user control of the heap? | | The mechanism should be made clear in the introduction as | browsing the documentation doesn't make it clear to me. | jfecher wrote: | The plan is to give users control through the Allocate effect | which can be handled in any way desired as long as it returns | some memory. It is similar, but easier to use since it is an | effect, to zig's approach of "pass the allocator everywhere." I | say easier to use since effects are automatically passed around | where necessary and propagated via function signatures and can | be inferred. | | The specific design is still in question though. For one, an | effect like Allocate will be almost ubiquitous throughout each | function so care is needed to not clog up signatures too much. | There's a few potential solutions here if you're interested. | From including Allocate within the row-type of a larger effect | like `IO = can Allocate, Read, Write, ...` to encouraging | effect inference on functions versus manual annotation. | skavi wrote: | In the case of map operations, Ante is using Rust style | iterators over collections. In Rust, iterators are just as fast | as loops. | | The author said somewhere else in the thread they want to | switch to generators implemented with effects eventually. | djoldman wrote: | dotproduct [1, 2, 3] [4, 5, 6] //=> 22 | | ???????? | jfecher wrote: | Whoops, typo. I'll update the website | a3w wrote: | 4+10+18, so 32 base 10, or hex: 20, but in base 15 this | actually works out. | tomcam wrote: | This had me giggling, thank you | sargun wrote: | Is there a reason not to force a linear typing system (at first) | -- a la mercury? To simplify the lifetime analysis? And then in | later versions that can be relaxed to affine typing, and then | subsequently normal lifetimes? | jfecher wrote: | Lifetime inference originates from region inference which is | actually completely unrelated to linear/affine/uniqueness | typing. Uniqueness typing can definitely complement lifetime | inference, but isn't really generalizeable to it. | Escapado wrote: | The syntax looks a little funky to me but it's still quite | interesting. Is there a reason why you would make fn(1) and fn 1 | equivalent? For me personally it makes readability worse and | looks strange when chaining functions like in their last example | on their landing page. | | On mobile horizontal scrolling through the code snippets will | trigger switching to the next snippet on my phone. | louthy wrote: | > Is there a reason why you would make fn(1) and fn 1 | equivalent? | | 1 and (1) are isomorphic. A single term tuple can be converted | to the single term, and vice versa. Having an implicit | conversion doesn't seem too crazy. | | The biggest issue I suspect would be confusion about the most | idiomatic way, or a mix of styles in real-world code bases, | that causes confusion or inconsistencies (increases cognitive | load for the reader). | IanCal wrote: | I'm not sure I like a single item tuple being equivalent to | just the item. Can you ask for the length of a tuple? The | length of a tuple with two 100 element lists would be 2, and | if you looked at the tail the length would be 100. | layer8 wrote: | Right. In particular if you identify tuples with lists | (which seems reasonable), you run into typing problems, | because singleton lists/tuples suddenly have to be unified | with the element type. | dannymi wrote: | I agree. It's better to have single-item tuples still | require a comma. | | (1) - the number 1 | | 1 - the number 1 | | (1,) - a tuple with one item, which is the number 1 | | 1, - a tuple with one item, which is the number 1 | louthy wrote: | To be clear, I didn't say they were equivalent. They're | equivalent up to isomorphism, not equal. | [deleted] | derriz wrote: | I don't think this applies in this case. The brackets here | are used for resolving precedence only. They are a syntactic | feature which are not represented in the language semantics. | | Where brackets are used in some languages to construct | tuples, you generally need special syntax to represent the | case for 1-tuples, like Python where "(1)" is equivalent to | "1" but "(1,)" is a 1-tuple containing a single 1 value. | | Also in most FP semantics, x and the 1-tuple containing x are | not equivalent so the mathematical isomorphism doesn't hold. | The tuple itself could be undefined (bottom/unterminating or | null, if the language has such a concept), or could contain | an undefined value or could be completely well-defined. These | three cases are not represented in the semantics of the | unbundled value. | curryhoward wrote: | > Is there a reason why you would make fn(1) and fn 1 | equivalent? | | For the same reason you'd make 1 + 2 the same as 1 + (2). | Parentheses can be used to group arbitrary subexpressions. | HelloNurse wrote: | If fn 1 is an allowed function call syntax then fn (1) should | be synonymous because 1 is expected to be the same as (1) | except for a slightly different parse tree; but there might | be good reasons to make fn 1 not a function call (for | example, the implicit operator in a sequence of two | expression without parentheses could be string concatenation | rather than function application). | curryhoward wrote: | > the implicit operator in a sequence of two expression | without parentheses could be string concatenation rather | than function application | | You can design a language which uses `e1 e2` to represent | any binary operation you like, but I'd argue that function | application is more common than string concatenation, so | it's more deserving of that syntax. Plus, it plays nicely | with currying. | deltaonefour wrote: | Haskell does this. Basically languages that are following the | ML style have this syntax including Haskell. You must not have | experience with this family of languages at it is very common | and a huge part of functional programming. | | A good number of "functional programmers" only have experience | with JavaScript these days. | Escapado wrote: | Close, I saw this when dabbling with Nim and I remember I | found it confusing, since I mostly write TypeScript and that | is not a thing there so my post was mostly my ignorance | speaking. I guess when one is used to it it will not look | confusing at all! | tazjin wrote: | > Is there a reason why you would make fn(1) and fn 1 | equivalent? | | If your standard function call convention is just `f x`, but | you also support precedence operators, then `f (x)` | automatically becomes possible. | | It reminds me of an old Lisp joke, though: f | x -- too mathematical! (f x) -- too many parenthesis! | f(x) -- just right! | gpderetta wrote: | > f x -- too mathematical! | | :) yet we are very happy to use the same syntax in shell | scripts or the shell itself. | lyxsus wrote: | We're not, but your point is still valid. | georgyo wrote: | I've heard this argument before, but this isn't true. | | In shell, f x y z, x y z are all augments to f. Doesn't | matter if f takes one argument, all are passed to the | function. | | With many functional languages this gets very confusing. IE | what ie what does `f f x` do? In shell I know for sure. | | In the example f f x, it might be easy to parse. But in f x | y z, any of x y z might be functions. | nemaar wrote: | Most functional languages parse a b c d e f as a(b, c, d, | e, f), it does not matter what b, c, d, e, f are. Do you | know any language where this is different? | dwenzek wrote: | OCaml and Haskell parse `a b c d e f` as `((((a b) c) d) | e) f`. | jerf wrote: | And while _different_ than Algol-descended languages, I | don 't think that's particularly _confusing_. (Not that | you were saying so, just continuing the conversation.) | You can put together a confusing expression with it, but | I can put together confusing things with the Algol syntax | with not too much effort too. I 've got the source code | with my name on the blame to prove it. | georgyo wrote: | I thought more languages did this but at least nix and | ocaml do not actually behave like I thought. | | In Ruby however it is a bit more ugly def | f x x + 1 end puts f f 1 | | > 3 | the_af wrote: | I don't understand your objection, what output would you | like to see instead? | lgessler wrote: | GP's point is that while yes, we know since `f` has arity | 1 there's no ambiguity, in general you might not have the | arity of any given function fresh in your head, and | therefore can't tell (in Ruby) just from looking at `f f | 1` whether it means a single invocation of an arity 2 | function, or two invocations of an arity 1 function | the_af wrote: | Ah! Right. It helps to have all functions be of arity 1 | to disambiguate, yes. | tomp wrote: | No functional languages do that. | | OCaml parses a b c as ((a b) c). In case the compiler can | determine that a is a function taking 2 arguments, it | will optimise the code, so that it's effectively a(b, c). | But in general, that's not possible, especially in the | case where the compiler determines that a is a function | with a single argument (in which case, it's return value | must be another function, which is in turn called with c) | or when a is a first-class function (e.g. passed as an | argument) | zeckalpha wrote: | Many counter examples to this, xargs for one. | georgyo wrote: | xargs is not a counter example. It is not a shell | builtin, it is a program that takes a bunch of string | arguments like every other program a shell would call. | | xargs -0 -n1 bash -c 'mv $1 ${1//.js/.ts}' -- | | Everything to the right of xargs is a string argument | passed to xargs. | zeckalpha wrote: | So an identifier representing a function? Isn't that the | same for most languages? | layer8 wrote: | I guess the difference is that nesting calls (commands) is | much less common in the shell, and (closely related) | commands don't really have a return value. | DougBTX wrote: | On the shell, it is all the other way around, so to use | command substitution it is like this: f | $(g x) | | To pass the output of g x to f. | gpderetta wrote: | is it? In practice I find that my shell one liners are | orders of magnitude more complex than what I would dare | to write in any other 'proper' language: | grep "hello ($(cat patterns.txt| tr '\n' '|'|grep ')$^)" | <(ssh other.host "cat ~/file.txt") |tee >(grep 'a' | >As.txt) >(grep 'b' > Bs.txt) | | [yes, gratuitous use of cat, sue me] | mellavora wrote: | As an old lisp fan, I never got this. (f x) and f(x) have the | same number of parenthesis. | | and the nice thing about (f x) is that the parenthesis group | f with x; so you have the whole call inside the (). | Consistent and simple to understand. | | vs i.e. print(f"a string"), where it isn't even clear that | the "f" is a function call. | layer8 wrote: | > the nice thing about (f x) is that the parenthesis group | f with x | | The drawback is that they are put on the same level, | whereas in most people's minds the function is a | fundamentally different thing from the argument(s). The | "f(x)" syntax reflects that asymmetry. | agumonkey wrote: | What creates that distinction ? in the lisp / fp world | you quickly stop considering functions as separate | entities. | layer8 wrote: | The function represents the operation or computation you | want to perform. The arguments represent inputs or | parameters for that operation or computation. | | Of course, theoretically you could also view the function | as a parameter of the computation and/or the arguments as | specifying an operation (in particular if those are also | functions), but for most concrete function invocation | that's not generally the mental model. E.g. in "sin(x)" | one usually has in mind to compute the sine, and x is the | input for that computation. One doesn't think "I want to | do x, and `sin` is the input of that operation I want to | do". One also doesn't think "I want to do computation, | and `sin` and `x` are inputs for that computation". It's | why you may have mentally a sine graph ranging over | different x values, but you don't imagine an x graph | ranging over different functions you could apply to x. | agumonkey wrote: | But even in high school topics start to talk about | functional equations (calculus, e/ln). I'm not sure the | <function> vs <value> doesn't come from the mainstream | imperative paradigms and only that. | tikhonj wrote: | The distinction isn't between functions and values in | general, it's between _the function being called_ and | _the arguments passed to the function being called_. The | difference isn 't in the things themselves, it's in the | role that they play in the specific expression we're | reading. | agumonkey wrote: | This argument is rather strange. Maybe for people who | never interacted with different -fix notations ? Human | language binds concepts with arguments in all kinds of | direction .. I'd be surprised this is enough to annoy | people. | layer8 wrote: | Natural language isn't precise and a lot is inferred from | context. Exact order does often not really matter. In | formal languages, however, you want to be as unambiguous | and exact as possible, so it makes sense to use syntax | and symbols to emphasize when elements differ in kind. | | Incidentally, that's also why we use syntax highlighting. | One could, of course, use syntax highlighting instead of | symbols to indicate the difference between function and | arguments (between operation and operands), but that | would interfere with the use of syntax highlighting for | token categories (e.g. for literals of different types). | agumonkey wrote: | We also alternate between various positions in | programming languages, so it's not a matter of formal | precision. | layer8 wrote: | Not sure what you mean by "alternate between various | positions". | junon wrote: | That's the joke. The number of terms doesn't change, and | the last two have the same number of parens. The statements | relate quantities of those things as though they're a | problem, when in reality the "just right one" only changes | the order slightly. | | Hence the joke. It's one of those jokes that earns a loud | sigh from me, rather than a chuckle. | YetAnotherNick wrote: | > (f x) and f(x) have the same number of parenthesis. | | That's the joke | quickthrower2 wrote: | I have heard that if you count { and ( as parens, a Java | program for example has just as many parens as. lisp one. | | A lisp paren can do both jobs: expression and scopes. | layer8 wrote: | > A lisp paren can do both jobs: expression and scopes. | | Using different symbols for different purposes makes | sense, it helps humans to parse correctly faster. | quickthrower2 wrote: | It does, but there are still many different purposes. In | JS: | | ( can mean: function call, part of an expression to be | evaluated first, regex capture group | | { can mean: scope [of ... loop, if, lambda, function | etc.], object definition, string interpolation code i.e. | ${..}, class definition | | There are probably other things I am not thinking of. | | The one that trips me in JS is code like x.map(v => | {...v, v2}) breaks because the compiler sees the { as the | beginning of a scope, not the object definition I | intended. | | The working solution is x.map(v => ({...v, v2})) | | But x.map(v => v+1) is allowed. | | I don't think the compiler could figure out what you | meant because an object definition might be a lambda | function body. For example { myvar } can be both an | object definition, and a function that returns myvar. | gorjusborg wrote: | Sure, why care about splitting algorithms into functions | and naming them appropriately when you can just write it | all in a 3k line function. | kazinator wrote: | That's right, which is why the parenthesis is combined | with a leading symbol like (let ...). | | Mainly, you don't look at the parenthesis when reading; | you look at that _let_ and your eyes rely on indentation | for structure. | hyperhopper wrote: | Okay, but now you have to look at more things. | | Also, the ending paren doesn't have anything to help you | see what it is on its own. | lispm wrote: | Just ignore the parentheses. Setting them to the | background color may enforce that. | kazinator wrote: | You're not supposed to look for ending parentheses in | Lisp; getting the right number of them is more or less | your editor's job, and they are maximally stacked | together like this: )))), as much as the given nesting | and indentation permit. Given some (let ..., the closing | parenthesis could be the third one in some ))))) | sequence; you rarely care which one. If it's not matched | in that specific ))))) sequence where you expect, then | that's a problem. | | )))) is like a ground symbol in a schematic: | (+5V (+10V (-10V | (INPUT3 ...)))) ----- local | "ground" for all the above (different | circuit) | layer8 wrote: | That doesn't seem very user-friendly. ;) Or at least | that's always my perception when programming Lisp. | | As a side note, I believe that different people | fundamentally have different programming languages that | objectively suit them best, due to differences in their | respective psychology and way of thinking and perceiving. | It's interesting to discuss trade-offs, but in the end | there is no single truth about which language is better | overall -- it depends on the task and on the person. | There's no "one size fits all". What's valuable is to | understand why a certain syntax or language might work | better for certain people. | hota_mazi wrote: | It's a stupid joke. | | The negative reactions that a lot of people have toward | Lisp's parentheses are not because of the call function | syntax but because parentheses are used everywhere else in | Lisp's syntax. | agumonkey wrote: | I wish we had stats about sexps (we shall call it the | kinthey scale). As a kid what you said was the first thing | my brain caught on. It compresses the number of things I | had to remember it's ~always (idea arguments...). We can | now discuss interesting problems. | throwaway17_17 wrote: | I had the same feeling when I first started using a lisp | (which was Scheme to work through SICP and the | Ableman&Sussman MIT course). I was utterly entranced by | the simplicity and uniformity. It was absolutely a factor | in the way syntax works in my personal language (which I | use for almost everything in my day to day). I really do | agree that learning a lisp can truly expand the way in | which a dev views and thinks about code. | agumonkey wrote: | Brown university PLT labs also vouched for this | approache. Their textbook starts with a descriptions of | sexps as syntax saying that's the only parsing theory | covered here. | arethuza wrote: | Pity it doesn't include a line for: x f | silon42 wrote: | x.f and x.f() | cwillu wrote: | That line necessarily comes after all the arguments about | it. | xico wrote: | It seems the parenthesis are only used to indicate the | priority, like in Haskell, so you wouldn't really use them for | `fn (1)`. | eatonphil wrote: | Those two function calls are the same thing in OCaml and | Standard ML. | aloisdg wrote: | Coming from OCaml, F# uses the same syntax. Works very well. | [deleted] | aconst wrote: | Hello, I just wanted to point out that if I had not read comments | here and had not looked for a second example linked to "Job", I | would not have realized there were dots under the first example. | You may want to make them more visible ^^; | coryfklein wrote: | Very interesting to see you essentially replace Tuples with Cons! | I'll be following that development for sure. Coming from Scala, | Tuples are entirely how you represent Product and then that has | tons of downstream implications on case classes and constructors. | This leads to situations like Scalaz creating NonEmptyList to get | Tuple-like behavior out of List. So your approach has the | potential to unify these similar but different types and I find | that fascinating! | Garlef wrote: | Looks great! | | A few questions (hopefully the author still reads it): | | * Any plan for support of arrow/monad comprehensions? | | * Semi-related: When it comes to generators it might be worth to | consider making them clonable (see | https://github.com/pelotom/burrido) | jfecher wrote: | (1): No support, some monad or early-error-return sugar used to | be considered but effects cover most of the usecases of monads | and are easier to use so I plan on emphasizing them. As for | arrows, I have to say here that I've actually never used them | in haskell so I don't know how useful they'd be. | | (2): Thanks for the resource, I'll look it over :). I remember | seeing a vaguely related note that multicore OCaml used to | provide a `Obj.clone_continuation` function but no longer does. | Of course, ante's algebraic effects design is rather different | so that may not apply anyway. It may depend on the Generator | whether it is cloneable or not. Some generators are just | functions that can be arbitrarily cloned, but others are | closures which may have restrictions in whether their | environment is cloneable. Ante's clone story in general needs | more work. | ncmncm wrote: | This looks like it could become a very interesting language. | Unusually for a new language, I spotted zero red flags. | | I did do a quick ^f for "destructor" and "drop"... | | The intro mentions memory management, and inferring lifetimes. | How do I make something happen at end of lifetime? | bruce343434 wrote: | RE lifetime inference: how does it infer lifetimes for mutually | recursive functions that return refs to what would be local data? | Does it automatically infer a dynamic stack? | jfecher wrote: | Lifetime inference compiles to destination passing so for most | cases a single stack allocation in a prior function can be | used. For your example of mutually recursive functions | allocating in a loop the destination would be a region that | will grow dynamically like an arena allocator. Since refs are | typed, ref elements of the same type will be allocated next to | each other in memory. | | You don't touch on it, but there are some more difficult cases | with lifetime inference as well. Namely branching the compiler | must decide whether or not to extend a refs lifetime. This and | a lack of granularity in container types are known problems | with region inference (they lead to more memory than necessary | being used by assuming the longest lifetimes), and are things I | hope to tackle. | jfecher wrote: | Hello, author here! As the website says, the compiler itself is | still in a very early state where basic things like functions, | types, traits, inference, monomorphisation, and codegen are | implemented. The fun stuff of algebraic effects, lifetime | inference, and refinement types are not however. Though I can | elaborate on implementation strategies of these for anyone | curious. For example, algebraic effects in existing languages can | be quite slow at runtime due to the use of continuations, the | translation to a monad stack, and/or dynamically finding handlers | at runtime. It is my plan to monomorphise them away and inline | all these cases as in the following paper (1). With this, | handlers are inlined into functions, continuations are normal | closures, and continuations that aren't called multiple times are | inlined. | | (1) Zero-cost Effect Handlers by Staging: | http://ps.informatik.uni-tuebingen.de/publications/schuster1... | vanderZwan wrote: | As a person named Job I'm a little confused by the second | example on the website, but that's probably my own | interpretation bias :p | | Joking aside, looks cool, good luck with the project! | funny_falcon wrote: | I'd really like to find "the low-level functional language" | without "the fun stuff". Or at least it should have simple and | boring subset that is usable without "the fun stuff". | | Something like Caml Light - precursor to Ocaml - but with | translation to C instead of bytecode, so it will be fast and | will have comfortable integration with C libraries. | cardanome wrote: | > fun stuff of algebraic effects, lifetime inference, and | refinement types | | > I'd really like to find "the low-level functional language" | without "the fun stuff" | | But current stable Ocaml has neither of the "fun stuff" | mentioned and compiles to native code. So isn't that exactly | what you want? | | It doesn't even need lifetime analysis because automatic | garbage collection be praised. | | And algebraic effects are awesome. Sure they are not | mainstream yet but conceptional there is a strong parallel to | the Common Lisp condition system which is quite established. | Not sure why you wouldn't want to have them. Also it is still | a long way until we will see them used in user facing stuff | in Ocaml. | funny_falcon wrote: | Ocaml doesn't compile to C. Sometimes having translated C | source is major gain. | | I'm not Ocaml implementation expert, but I suppose Ocaml | exception handling and garbage collector could be tricky to | aware about when one extend or embed Ocaml. | | To be honestly, my fellow did Ocaml embedding once. He made | able to load natively-compiled plugins written in Ocaml | into C based software. And it worked. I didn't dig into | details, though. There was at least one bug regarding | garbage collection (and it happens that I fixed it). | Drup wrote: | `ocaml-ctypes` currently supports "reverse-bindings" | (making OCaml functions available from C) out-of-the-box | and mostly takes care of the intersection you are talking | about, so this already works quite well. | | The only gain from emiting C code is portability to weird | architecture that would be covered by C compilers but not | the OCaml one; which is arguably a pretty niche use-case. | throwaway17_17 wrote: | This was basically my motivation when I designed the language | I use for my day-to-day tasks where I program. I just wanted | a 'functional' sort-of language that compiled to C. | | Even though I'll never release it, it was a rewarding | theoretical and practical exercise. | | As an aside, I really love how detailed and example-ful | Ante's site is. It looks like a 'fun' language and I hope the | creator keeps working on it. | funny_falcon wrote: | May be it is time for "coming out"? (Excuse me for the | joke). | | Really, perhaps your language is The Next One. | jfecher wrote: | This is definitely an interesting thought. My thinking is | that "the fun stuff" tends to help make the language more | functional, so removing it you are left with a more | imperative language resembling a C clone with traits and type | inference. Then if you want easier C interop you must remove | traits and either remove modules as well or provide a | standard method of mangling module names into function names. | At that point I think you may as well use an existing "better | C" language like Odin, Zig, Jai, C3, etc. | [deleted] | trumpeta wrote: | what makes Ante low level? Just from a cursory look over | the website seems pretty high level to me. | jfecher wrote: | My definition of low level is no tracing GC, values are | unboxed by default, and users still have control to do | low level things (raw pointers, other unsafe operations) | when needed, even if it is not the default. | rayiner wrote: | Very neat project! I noticed that Ante doesn't have explicit | region type declarations. As I recall, existing algorithms | implemented for ML can sometimes infer very large regions which | causes memory usage to balloon. It looks like smart pointers | are part of the plan to address that possibility, but I'd love | to hear more about your thoughts on memory management. | jfecher wrote: | Correct, a key goal is to have no explicit region/lifetime | annotations. There have been several papers on region | inference after the originals by Tofte & Taplin, all | attempting to refine the original analysis by inferring | shorter lifetimes. First by analyzing when a region can be | safely emptied and re-used, then by abandoning the stack | discipline, etc. Unfortunately, none of these are viable in a | real program in my opinion. Although they each infer shorter | lifetimes in a few cases the core problem of "collections | will unify the lifetime variables of all elements in the | collection" and "branching on a value and conditionally | returning it extends its lifetime, even if the branch was not | taken" remain unsolved. | | An ideal solution to me needs to solve these problems. Since | there is already a large body of research trying to address | this on the static side and failing, I believe it needs to be | solved with runtime checks. The specifics of which I'm still | exploring but its worth mentioning these would only be | necessary to tighten existing lifetimes so one can envision | annotations or compiler options to elide these if desired. | Lifetime inference in MLKit (and I believe ante as well) | tends to speed things up by turning more dynamic allocations | into stack allocations, so there is some room there for | runtime checks without making the result more expensive than | the version with dynamic allocation I believe. | raphlinus wrote: | Hey, thanks for calling my work "not viable!" (Co-author of | "Better Static Memory Management" here) | | Seriously, this looks promising and I'm very interested to | see where it goes. | rayiner wrote: | I'm curious what the rationale is for not making regions | explicit in the source code. It seems like a downside of | region inference for a systems language is the | unpredictability of the inferred regions. | charleskinbote wrote: | Thanks for sharing! | | The dot product example gave me pause because map2 seems to be | the same as zipWith. Does that exist in Ante? Without context I | might have thought map2 was going to act as bimap. Take that | for what you think it's worth :) | | Also I might be having a brain fart -- but isn't the dot | product in your example equal to 32? | jfecher wrote: | map2 is indeed another name for zipWith. I believe I got that | name from Racket if memory serves. Compared to zipWith I like | its symmetry with the 1 argument map. I also wasn't aware of | bimap! I can't seem to find a function of that name online, | though I did find the BiMap haskell package, is that what | you're referring to? | | And yes, the dot product should be 32, thank you :) | pwm wrote: | Not OP but they meant https://hackage.haskell.org/package/b | ase-4.16.1.0/docs/Data-... | | As you have the normal map function for Functors (using | Haskell): > :t fmap fmap :: Functor f | => (a -> b) -> f a -> f b | | you can have bimap for Bifunctors: > :t | bimap bimap :: Bifunctor p => (a -> b) -> (c -> d) -> | p a c -> p b d | | which specialised to pairs is: > :t bimap | @(,) bimap @(,) :: (a -> b) -> (c -> d) -> (a, c) -> | (b, d) | eruditely wrote: | I was wondering why the dot product wasn't 32! Good thing I | checked. Also this seems like a cool new language, I need | to learn a functional language, I wonder how far this | language will go in development. | PowerBallZed wrote: | I don't see a LICENSE file anywhere in the repo or the website. | Nobody is allowed to use Ante? | airstrike wrote: | > Nobody is allowed to use Ante? | | :eyeroll: | postingforonce wrote: | https://docs.github.com/en/repositories/managing-your- | reposi... | | >However, without a license, the default copyright laws | apply, meaning that you retain all rights to your source | code and no one may reproduce, distribute, or create | derivative works from your work. | airstrike wrote: | What do you think is more likely, that the author forgot | to add a LICENSE file or that he actually doesn't intend | for ANYONE to use the language he created? Give me a | break | b3morales wrote: | Looks like a great start! Your attention to programmer | ergonomics is admirable. Added to my weekend hacking reading | list. | vanderZwan wrote: | As a person named Job I'm a littly confused by the second | example on the website, but that's probably my own | interpretation bias :p | | Joking aside, looks cool, good luck with the project! | Sentient-HN wrote: | Interesting, well have to see how it develops in the future. | Although, I normally dislike languages without {}, but I suppose | that is just preference. | timbit42 wrote: | I hate having to press Shift to get braces. Let's use square | brackets which require no modifier key. | throwaway17_17 wrote: | Just a question: but are you considering the frequency of | usage for a particular symbol in that suggestion? If {} is | for opening/closing functions or code blocks/scope AND [] is | for making arrays, lists, or some other data structure: do | you only want them switched where, idiomatically, where there | are less function definitions vs instances of the data | structure? Or is it an overall objection to any shift+key | operator in a basic syntax? | zonotope wrote: | Why, why, why with the significant whitespace? That makes it a | non-starter for me. | rmrfchik wrote: | Age of "var, val" is over. Now age of "significant whitespace". | If seriously, seems like indent syntax is default to go | nowadays. Python has way too big influence on language | designers. | pjmlp wrote: | ML languages did it first. | jfecher wrote: | I'm not a fan of python (it handles significant whitespace | somewhat poorly). Cases like mixed tab-space whitespace and | single-line only lambdas are python-specific problems for | example. | | I chose it mainly because I like the style and I haven't | found it to be an issue in practice yet, especially with | flexible rules for line continuations. The biggest detriment | to me is the lack of auto-formatters for indentation. | | Edit: I'll add to this a point I haven't seen discussed | before about functional languages using the `f a b` syntax | specifically. Without significant whitespace you cannot | really have semicolon ellision with this syntax since almost | any line may be interpreted as a function application of the | next line. E.g. `a = b + c` and `foo 32` as subsequent lines | would be interpreted as `a = b + (c foo 32)`, hence why ocaml | requires semicolons. For some people semicolons are fine, but | they're just noise to me :) | | There's more detail on the specific significant whitespace | scheme used on the website here: | https://antelang.org/docs/language/#significant-whitespace | strbean wrote: | Regarding single-line only lambdas, I think that isn't an | intractable issue with the grammar. | | Guido van Rossum basically said parsing them is hard, and | he doesn't like lambdas in the first place, so it won't | happen. [0] | | Similar to "tail call optimization encourages dirty FP | nerds" logic. Terrible decisions IMHO. | | [0]: | https://www.artima.com/weblogs/viewpost.jsp?thread=147358 | jfecher wrote: | Agreed. Other whitespace sensitive languages like | Haskell, F#, and Ante handle multiline lambdas just fine | rmrfchik wrote: | You claim, significant whitespace helps mitigate "goto | fail" errors. As for me, indent syntax allows more human | errors during refactoring. When moving code blocks around, | you can easily misplace whitespaces and compiler/editor has | no ability to help you. | jfecher wrote: | This is definitely an issue, but isn't one I run into | often. Just like if I'm pasting code in rust I need to | make sure it is inside or outside an if statement, I | likewise need to ensure pasted code in ante is | inside/outside the if statement by looking at its | indentation relative to the previous line. The compiler | can help somewhat here, just not a lot. If your | indentation is not on an existing indentation level you | will get an error since you cannot randomly start indent | blocks in your code. I do think the biggest detractor of | indentation-sensitive syntax in general is the loss of | auto-formatting indentation though. | PontifexMinimus wrote: | That and Haskell. | hyperhopper wrote: | You forgot to say why or provide any rationale. | ricardobeat wrote: | Note to the website authors: the carousel with examples is not | usable on mobile - trying to scroll the code swipes away to the | next item. Would be better as just a series of example blocks. | sexy_seedbox wrote: | On mobile, I just go to [?] then "Desktop site", much wider | display. | qwertox wrote: | Yet it's great on desktop. What I liked the most about it was | that it doesn't auto-advance. | | Generally, the entire site has a really nice design, at least | on desktop. One of the prettiest I've seen. | anentropic wrote: | Even on desktop I wanted to be able to swipe (scroll gesture) | or arrow key to advance the slide, but have to click the | little dot icons | qwertox wrote: | I was able to drag it sideways with the mouse. | [deleted] | PontifexMinimus wrote: | Carousels should simply be abolished. Just have paragraphs of | text/image, one after the other. | | Also, black text on white background is easier to read. | | And don't make your web design to complex for Firefox reader | mode to work: if you do, that's a sign you're doing it wrong. | jfecher wrote: | Ah, yes. The carousel has been a source of frustration | especially on mobile for designing the website. Perhaps | disabling swiping to scroll on the carousel on mobile would | help. Or defaulting to use a dropdown to select the example on | mobile instead. | messe wrote: | It's broken on desktop as well. I was trying to select some | example code to copy and paste it in a comment here, but that | just click on it drags the carousel rather than allowing me to | select text. | jasonhansel wrote: | This is cool! I _really_ like the syntax for algebraic effects, | which would be great for mocking. I also like the way that | "impls" can be passed either explicitly or implicitly; the lack | of global coherence seems like a reasonable tradeoff. Have you | thought about using existential types to support dynamic dispatch | (like Rust's "trait objects")? | | That said, I'm a bit skeptical of the utility of refinement types | (and dependent types in general). They greatly increase a | language's complexity (by obscuring the type/value distinction | and making type inference and checking more complicated). I'm not | sure the benefits are worth it, particularly because of the so- | called "specification problem." | jfecher wrote: | Thanks! IMO it is very important effects have an easy syntax to | read/understand since they will be both used pervasively and be | foreign to most new users. I do have an idea for "traits as | types" which would cover static and dynamic dispatch: | https://antelang.org/docs/ideas/#traits-as-types. So a use like | `print (x: Show) : unit = ...` would be usable with static or | dynamic dispatch and more complex cases like `print_all (x: Vec | Show) : unit = ...` would use dynamic dispatch always. Perhaps | this may be too confusing though, and there are other | considerations as well which is why its only an idea for now. | | Your concern on refinement types is definitely valid and is | something I've been thinking about. They are certainly useful | in some cases like array indices or passing around certain | predicates but whether these are useful enough to offset the | implementation cost and brain tax is an open question. | dixego wrote: | What is the specification problem? I don't think I've heard the | term before. | myco_logic wrote: | This looks really lovely, I look forward to following the | maturation of Ante in the future. I've often thought that the | niche of a general purpose low-level FP language was a promising | space. The only other langs that fit in there are ATS[1], which | is notoriously complex/difficult-to-learn, and Futhark[2], which | is more GPGPU/scientific-computing specific. | | We've got Rust, which is essentially a C-style lang that steals | all kinds of goodies from the ML family; it's nice to see Ante as | a kind of inverse to this: i.e. an ML style lang that borrows | some of the nice bits from traditional imperative langs (and | hopefully maintains their performance characteristics). | | Looking through the language tour there already seem to be a | plethora of very sensible/ergonomic design decisions here. The | 'loop' and 'recur' keywords are a great feature, making a nice | little functional nod towards while loops. As a long-time Haskell | user the explicit currying initially turned me off, but after | seeing a couple examples I can see how it's actually a very | reasonable solution, and moreover the ability to curry out of | order is really nice instead of having to use 'flip' or similar | combinators (as a side note, the explicit currying reminds me a | bit of APL's a and o arguments in dfns, a feature I'd love to see | pop up more). The paired tuples also seem like they'd be a | pleasure to use; certainly a bit more flexible than tuples in | other ML style langs. Making '.' the pipeline operator is also a | smart bit of syntax, and I can see it being very accessible to OO | programmers in that it looks (and acts) like the method chaining | they're familiar with. Refinement Types seem like a good | alternative to full on dependent typing (ATS has an analogous | (and more general) proof system for ensuring things like array | indices are valid (an essential feature in a low level FP lang), | but it involves threading those proofs through your program which | seems much more clunky than what's presented here). | | Overall I'm really excited to see where Ante goes, it seems like | a very pragmatic functional language, something the world | definitely needs more of... | | [1]: http://www.ats-lang.org/Home.html | | [2]: https://futhark-lang.org/ | | EDIT: In regards to low level FP langs there's also Carp, Erik | Svedang's nifty little statically typed Lisp. It's like Scheme | with a Rust-y memory model: | | https://github.com/carp-lang/Carp | jfecher wrote: | Author here, thank you for your interest! There are definitely | a lot of small design decisions that add up in language design, | from using `|` for match cases to avoid double-indentation, to | the use of pairs over tuples, to how methods are resolved and | chained, it is nice to have others appreciate the small things | sometimes :). | | I'll avoid posting it here but if you do want to follow ante's | development the best place is on its discord which is linked on | the github page. | [deleted] ___________________________________________________________________ (page generated 2022-06-17 23:00 UTC)