1. 45

You may submit ONE feature for LOBSTERS-LANG.

Otherwise the great Lobster In The Sky may curse you.

That is all. The slate is ready for your chisels.

    1. 39

      Comments start with ( and end with ). They may only go at the start of lines (can have whitespace) or at the end of lines but span multiple lines, i.e. like a block selection in vim or other editors.


      ( This is
      an example
      of a
      comment )
      somecodehere blah blah ( Similarly
      somecodehere blah blah   this is also
      somecodehere blah blah   a comment
      somecodehere blah blah   cool right? )
      1. 26

        That’s pleasingly cursed.

      2. 4

        On top of that, I’d like to add that LOBSTERS-LANG uses non-comment parens for sexprs. That way if you forget to add/delete whitespace, you can easily disable code or try to execute comments.

      3. 3


        1. 3


    2. 29

      There is a “GOTO” keyword, but instead of something as prosaic as affecting the internal control flow of the program, its argument is in fact an IP address, and denotes that execution of the program immediately moves from the current machine to the indicated machine.

      1. 18

        This of course is mirrored by its sister keyword COMEFROM which, thanks to the distributed nature of GOTO, can redirect any currently executing code on a remote server to be halted and resumed locally.

      2. 3

        Ah, this is a feature in General Magic’s programming language Telescript.

    3. 27

      array indexes begin at 2

      they actually begin at 1, but 1 always contains the string constant “LOBSTERS-LANG” so that u remember what language you’re programming in

      1. 18

        I’m reminded of the following quote.

        “Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration.” - Stanley Kelly-Bootle

        1. 6

          floats can also be used to index arrays, but they combine the values in each index in proportion to the float value

          1. 1

            Another option is that float indexes are rounded to integers using stochastic rounding.

      2. 7

        This is wonderful because it forces the type system to either accommodate mixed arrays or only support string arrays.

      3. 3

        Urbit’s language Hoon starts at 2. Everything about it is truly cursed.

        1. 8

          cursed language from a cursed project

        2. 2

          Got a link for the curious? The docs[2] suggest that array indexes begin at zero.

          [2] https://developers.urbit.org/reference/hoon/stdlib/2b

          1. 3

            It’s true, there is a more reasonable array access. But the first thing the docs introduce is the more primitive binary tree access, which you can see described here: https://davis68.github.io/martian-computing/lessons/lesson05-syntax.html – the “2” is the equivalent of (car), “1” is the identity function, and “3” is (cdr)

            Actually maybe it’s Nock that introduces this notion (though it’s also directly exposed in Hoon)… Nock is basically the bytecode underlying Urbit, which sets the stage for a terrible project, with stuff like 1 is false and 0 is true. Hoon still manages to add its own terrible weirdness, such as hexadecimal numbers as a type (just as a starter… the list of bad decisions in Hoon is very long).

        3. 1


    4. 26

      Here we introduce a new refinement and override function called well_actually which can be used to contradict or possibly pre-empt previous expressions/blocks of code. It may only be used politely.

    5. 24

      Operator Underloading

      You can specify values for which certain operators are illegal. For example, the division operator won’t compile if the divisor can’t be proven to be non-zero. But this is generalized to any value of any type and any operator.

      1. 5

        It seems like you want a mechanism for expressing partial functions: i.e. this function is valid only over this range of inputs. For this specific example, you also want a generalisation of Pascal / Ada-like range types, so integer division is something like /(Int, [INT_MIN,-1] | [1, INT_MAX]) : Int. You can’t implicitly coerce an integer to the type of the RHS unless you have either an explicit check that it’s not zero.

        The alternative representation of this is that divide is defined over all integers but it returns an option type, something like /(Int, Int) : Int | DivideByZeroError and then you must explicitly check the return value. The former form is nicer because it lets you elide the check on the result if the type system can statically prove that the divisor is not zero.

      2. 5

        Okay that’s actually kinda neat.

        1. 4

          See also dependent types?

          1. 5

            But make it feel like c++ specialization…

    6. 22

      Bogo-dispatch: the multiple dispatch paradigm is quite good in Julia and Common Lisp. The next-generation fault-tolerant language LOBSTERS-LANG improves on this design by allowing multiple definitions for each function. When a function is called that has more than one definition for the provided arguments then the runtime randomly selects one of the definitions to execute. This radically simplifies load balancing applications!

      1. 11

        Lets combine this with: Multicolored functions

        Each function gets annotated with how likely they are to be blocking, and for how long. Depending on this, the scheduler can choose to run functions differently. For example it can spawn known flaky functions (runtime statistics) multiple times, especially when they take longer, so re-running them would add substantial delay. This can be especially helpful with blocking Garbagecollection, where running multiple of them in parallel allows for more GC throughput and will utilize more cores. You can obviously nest runtimes for additional benefits (like spawning them multiple times).

    7. 17

      The undo keyword reverts the last mutation made by the current thread - mutations made via undo itself don’t count for this, so you can do it several times to travel back in time.

      1. 14

        Related: the @ operator when used with a variable lets you specify a particular index in the history of that variable. Positive numbers as the second operand mean steps backwards in that variable’s history.

        x = 3
        x = 5
        x = 2
        // => 2
        // => 2
        // => 5
        // => 3
        1. 20

          Thanks for bringing this up! It’s honestly my favorite part of LOBSTERS-LANG, and doesn’t get used enough. It’s super flexible too. If you use a negative number it’ll use linear regression to look into it’s future.

          // => 2.333
          // => 1.833
          // => 1.333
    8. 14

      synchronous, non-fallible functions must return values wrapped in a Certainly type

      1. 7

        Synchronous, non-fallible, atomic functions must return values wrapped in an AbsolutelyCertainly type.

        1. 5

          Asynchronous, fallible functions of course wrap their return values in a Supposably type

    9. 14

      In addition to checking syntax and types, the LOBSTERS-LANG compiler has a mental budget checker.

      Each language feature has an associated mental overhead budget associated with them. Simple stuff like arrays, structs, functions, loops add to your budget; complex features like macros, type parametrization, async deplete your budget. A program compiles only if its budget is greater or equal to zero.

      1. 15

        Oooh, so that’s why all those code samples had

        let balance_budget = [[[[[[[[[[]]]]]]]]]]

        in them!

      2. 4

        Yes, excellent, the language can be large and complex but you can only use a subset of it at a time

    10. 13

      To please adherents of all programming styles, nested code blocks have to be indented exactly 1 space, then 2 spaces, then 4 spaces, then a tab, then 3 spaces, then with a dot à la MUMPS, then no indentation, then turtles.

    11. 12

      The compiled binary is a also a valid Pdf describing the program. Preferably graphically with a mix of component diagram’s, railroad’s and call sequence’s.

      1. 4
      2. 2

        Should the PDF be created by handing the source code to a large language model?

        1. 5

          I would hope the compiler would have all the context to build the Pdf. I suppose a LLM would work great to create an executive summary the program.

      3. 2

        Antiviruses are going to be unhappy about that. They’re already grumpy about executable-pdf hybrids.

        1. 6

          That’s why it is always distributed in a pre-compiled Docker image. No anti-virus needs to know!

          1. 2

            Does the image also include a PDF reader?

            1. 2

              Of course not, that would violate the principle of “Do one thing and do it well.” The pre-compiled LOBSTER-LANG Docker image will contain the build chain to build a Docker image containing the PDF (LOBSTER-LANG-COOKED). I think what you’re asking about is the LOBSTER-LANG-PREVIEW Docker image that will, naturally, build the Docker image containing an appropriate viewer (LOBSTER-LANG-VIEW).

              I didn’t think I had to specify, but obviously this follows from LOBSTER-LANG’s attitude of

              Containers… all the way down.

    12. 19

      Y’all think you’re funny, but thus far, you’ve just described Perl 🤷‍♂️

    13. 11

      Unironically, only allow Unicode operators & adding new Unicode to the spec as needed. I never want to see forall or -> or . or when what the author really means is literally or or . In the same vein is = meaning equality (with being ineqality) & having assignment be either (ala APL) or (ala Pascal). None of that == or === stuff or telling me x = x + 1 makes sense somehow.

      1. 3

        This can actually be an interesting experience with an LSP or an IDE. Maybe, LaTeX can be coopted. You type \forall and LSP replaces it in-place .

        One downside is that forall is more accessible. Everyone who knows English can understand it. ∀ needs some background in order to read it. Another possible downside is potential issues with typos. Special character are single units. You can get it completely wrong while code would still compile but do unexpected things. Another issue is that Unicode has quite a few visually very similar characters. Remember when you war looking for that missing semicolon or brace, now imagine finding the issue with ≥ vs ⩾ in a few sousand lines of this code.

        1. 4

          The editor plugins for Julia use those latex commands to insert unicode symbols. Julia also has a whitelist of symbols that can be operators (most of which Base Julia doesn’t define any meaning for), which could be a nice solution to ≥ vs ⩾ by whitelisting only symbols that are more visually distinct (though Julia doesn’t do this).

          You can see all the operators Julia supports in the parser source: https://github.com/JuliaLang/julia/blob/62605cc40f51f5a921e5a9bdced1821afd49183f/src/julia-parser.scm

        2. 3

          These problems didn’t discourage APL, Agda, & Raku.

          Vim digraphs are an easy method of insertion. Often, programming should optimize for the reading experience & I find it easier to parse this way in my head, just as much to newcomers most familiar with equations from mathematics classes. Instead of fixing our languages, you get things like Yoda conditions and symbols having too many overlapping meanings while still assuming a $ is universally ‘easy’ to type.

          1. 2

            These problems didn’t discourage APL, Agda, & Raku.

            They prevented APL getting widespread adoption. I don’t know if they are the reason that Agda and Raku are not widely used.

        3. 2

          There’s an interesting compromise that I’ve seen in a few places: define ligatures in the font so that =/= or != is treated as a ligature that produces ≠. You type the ASCII text but it’s always rendered as the combined character.

          1. 1

            I guess, it’s possible to define ligatures for \forall and everything else but then you have to use that specific font and can’t code in Comis Sans. 😭

    14. 11

      Source is not stored in flat files, it’s SQLite databases all the way down/through/over/under/around

      1. 8
        1. 2

          Entirely satire? Or based on a true story? I’ve seen enough to not truly be sure. But thanks for sharing, thats a fun story.

          1. 2

            tdwtfs are mostly real submissions that happened

      2. 2

        That’s vaguely similar to how things work in Smalltalk, and truly is how things worked in VisualAge Smalltalk and VisualAge Java (though I assume the database was based on DB2). It ends up being very pleasant, but contributed to the Smalltalk-is-a-walled-garden issues (and probably expedited the transition from VisualAge Java to Eclipse).

      3. 1


    15. 8

      “If it compiles it works” guarantee.

      1. 2

        If it compiles it works.

        Perl, basically. Any bytes sequence is a valid program, you just don’t know what it does yet.

        1. 3

          Nah, this language has a much stronger guarantee since the two “it”s don’t have to refer to the same program. So you can do “if A compiles B works”. For Perl this would only hold if both A and B are written in Perl.

      2. 1

        Enterprise feature: if it doesn’t compile it doesn’t work guarantee. One less horror to haunt Ops’ folklore.

    16. 8

      The PLEASE keyword from intercal. Any statement may be preceded with it, and it has no effect at runtime, but programs with too few are rejected by the compiler for being rude, and programs with too many are rejected for being excessively polite. The appropriate number depends on how much sleep the compiler got last night.

    17. 7

      The global constant RANDOM which is a 64-bit field selected randomly at compile-time.

    18. 7

      I’ve always wanted inline localization for symbols:

      # file-level language choice
      written en
      -[en]> go(where: String)
      -[es]> irar(dónde: Cuerda)
      -[eo]> iri(kien: Ĉeno)
      -[nl]> ga(waar: Tekenreeks)
        coordinates = lookup_coordinates(where)
      # another file
      written eo 
      # I'd want a more universal keyword for "written in", or maybe a symbol?
      -[eo]> anonci(mesaĝo: Ĉeno)
      -[en]> announce(message: String)
      -[es]> anunciar(mensaje: Cuerda)
      -[nl]> adverteren(adverteren: Tekenreeks)
        ludi_sono().post_tio(-> printi(mesaĝo)) # play sound, and then print the message

      The developer’s editor would be responsible for replacing symbol names in the language of the developer’s choice.

      This would enable code internationalization and reduce the requirement to learn English to code. Koro was an experiment in enabling writing Go in Bengali, created by an acquaintance of mine. I experimented ~15 years with a Scheme shim that would have enabled writing Scheme in Esperanto.

      1. 3

        Hell, I’d like this for English. I didn’t like watching a coworker struggle his CSS errors because he was using background-colour, nor did I like getting errors using aria-labeled in HTML or AnalyzerNode in JavaScript.

        1. 3

          This could be solved by having an editor plugin ask the GPT4 API to correct/translate all words like these.

          1. 3

            GPT4 when I can run it without giving them my phone number.

            1. 2

              There might be local LLMs that are good enough for this task as well.

    19. 6

      Reserved keywords for AST manipulation. If I have

      AddAndSquare(x, y) {(x+y)²}

      I should be able to define

      MultAndCube = AddAndSquare EXCEPT + ← *, ² ← ³

      Also, keywords for querying the git repo, so that you can make “Bob edited the User class” be a compiler error.

      1. 2

        And here’s one that’s only 60% a joke: the subtraction operator should be +- instead of -. That way you could have kebab-case identifiers

        1. 2

          Only zero% a joke: allow - in identifiers instead by requiring spaces between operators and other tokens.

        2. 1

          Implying counter +-= 1 to decrement, I like it.

    20. 5

      The semantic use of color and font styles. We already assign meaning to capitalization, and use color for highlighting, this is just the next logical step.

      We can solve the function color problem by combining the colors, so if if you see yellow (red + green combined), you know you have an async mismatch.

      How about using strikethroughs to indicate a function is deprecated?

      Creating a process with a bold name means it gets a higher priority; one with italics is niced, instead (angry/happy emoji work too).

      Abstract types can be assigned a color, and then the editor picks country flags to indicate which types variables implement.

      1. 1

        like color forth

    21. 5

      Leaderboards. Whenever you run a unit test, it records your name and a handful of performance counters (instructions, cache misses), and publishes it. It shows you the worldwide histogram, and how your LOBSTERS-LANG friends did on the same problem.

      Two runs appear on the same leaderboard as long as the unit test is the same; the non-test code can be different. The most popular unit tests tend to use (deterministic) property testing, to make it harder to cheat by overfitting.

    22. 5

      A program is the programmer’s expression of how to automate some sequence of data changes, though that program may be incomplete or underspecified. In typical languages, running programs will handle these problems with exceptions. Where Lobsters-lang is special is that it fully follows through with this understanding to enhance the common method of error-handling. Uncaught exceptions go to the programmer’s phone. Program execution is paused until the programmer inspects/edits memory or the stack to resolve.

      1. 2

        Should be reasonable enough to do with a condition system as n Common Lisp.

    23. 4

      Operators to do arithmetic by asking a large language model for the answer. Who knows what 17 + 39 turns out to be.

      1. 4

        Or a generalization of this: a keyword that executes the following code block in an LLM. For example, in C-like syntax:

        llm {
            return 17 + 39;
    24. 4

      No differentiation between language and user code (a la FORTH). You can add operators at will.

    25. 4

      The language only supports ASCII

    26. 4

      S-expression syntax with ()’s.

      …we can have the comments require a space separating the parentheses from the contents. No space, it’s a function call, not a comment.

      1. 3

        a space separating the parentheses from the contents

        Fun fact: at least in Emacs Lisp, this means one intends the contents as data rather than code (which changes how the auto-formatter treats it).


    27. 4

      Everything is asynchronous. No more worrying about mixing red and green functions!

    28. 4

      A serious one: checked exceptions that automatically bubble up through callers that don’t declare themselves as only throwing a specific list of exceptions. Or put another way, checked exceptions where the programmer declares where the exception handling needs to happen, rather than declaring where it doesn’t need to happen.

      I believe that if Java had done its checked exceptions this way, they’d be regarded as a great language feature instead of a mistake.

      1. 4

        Not sure if I’m understanding you correctly, but you would need first-class Effect types for that, see https://koka-lang.github.io/koka/doc/index.html

        But yeah, I do believe that this is the best way to handle errors.

    29. 4

      Arbitrarily extensible syntax and semantics. Not mere macro syntax.

      For example, you can import datetime syntax into your module and then you can specify date and time literals in, say, ISO 8601 format without any Date.parse("...") wrapping or anything like that. Just datetime = 2023-07-28T17:42Z. Or if you fancy RFC 2822, you can have that, too: datetime = 28 Jun 2023 17:42.

      Same goes for other features. Say, the language doesn’t have classes by default, just records/structs. No big deal, import class syntax and you get your class syntax and all the machinery that implements classes, with inheritance, method calling syntax, and what not. Don’t want classes but still want objects? Maybe prototype inheritance will suit you more? Import that.

    30. 4

      Every function is annotated with how many dollars it costs to run (multiplied by the cartesian product of all collection typed arguments) and execution time requires a credit card number.

      Experimental extensions exist for other currencies but the standard library isn’t compatible with them so they go unused

    31. 3

      I’ve been meaning to opine on this as an unironic thought experiment, but maybe this will get me to rehost my blog

      async/await is the wrong paradigm. You really want yield/async

      Here me out: async as we know it colors functions. You want to have async at the call site. So the pattern is:

      fn foo() {
      x = async bar()  //Returns immediately with a Promise<i32>
      fn bar() -> i32 {
      for x in longRunningLoop {
      return 4

      This only requires that the language runtime have a way to handle these global keywords that could be replaceable— an alternate scheduler just like we already consider alternate allocators, malloc (eg, libc/jemalloc)

      For calls where you absolutely want to block, then just call the function, the yield is a no-op. This is equivalent to bar().await which is the far more common pattern.

      Then, the people implementing the lower level function have control over where they can be interrupted and the call stack doesn’t depend on calling the right function color.

    32. 3

      …that features a keyword probably which will execute the provided code with a 90% probability.

      1. 1

        INTERCAL has this! You can add a %x where 0 < x < 100 to a line to indicate it should execute with a certain probability.

        1. 1

          That was indeed my inspiration :-)

    33. 3

      ( Turing machine literals )

    34. 3

      Easy SIMD like how modern languages now have easy async.

    35. 3

      Built in mutation testing!

    36. 3

      Functions cannot return any value, they are all like void functions in C++. Instead, the canonical way to return information to the caller is through exceptions. Exceptions can be of any type and are neither required to inherit from a type nor implement some interface.

    37. 3

      Greedy macro unexpansion, which automatically compares subsets of AST nodes to refactor them into un-hygienic, gensym containing defmacro style macros. The result is unprecedented expressiveness making it trivial to maintain LOBSTERS-LANG code bases.

      1. 2

        Conceptually, this is something I actually really really want. I just can’t carve out the time to design and write it.

        I write Go for work and enjoy it’s stripped down nature. But the vertical space bloat and boilerplate enforced by the ecosystem (context.Context, if err != nil) kill me. It’s so hard to see the intent when it’s surrounded by so many mechanics.

        I want a macro compressor, entirely separate from the language. It could literally just be a presentation view in my editor. Open a Go file, highlight a block, and then “semantically compress” or “semantically expand” whatever I want.

        ctx := context.Background()
        manyObjs, err := superFaillible(ctx)
        if err != nil {
          return err
        var fullObjs []FullObj
        for _, o := range manyObjs {
          full, err := inflate(ctx, o)
          if err != nil {
            return err
          fullObjs = append(fullObjs, full)

        becomes something closer to

        // $ctx is magic. Autopassed to funcs w/ context args.
        $ctx :=  context.Background()
        manyObjs := superFallible()?
        fullObjs :=  range.filter manyObjs inflate($_)?

        No actual thought was put into that syntax, just filler to get the idea across. Realistically it’d be a lisp with some Go specific keywords and structures. It’s not about writing code at that level, it’s about being able to visually scan a file in Go. Since it’s not for writing, the syntax is unencumbered by ergonomics. It should just be immediately clear and unambiguously map to Go.

        1. 4

          That reminds me of a feature I’ve seen in a Java IDE, maybe around the time they added lambdas. Lambdas desugar to an inner class with one method, so the IDE would recognize an inner class with one method and display it more like a lambda.

          Something like this:

            new EventListener {
              void handleEvent(Event ev) {


          listenable.addListener((Event ev) => {
        2. 2

          I agree with the need, but not the conclusion. You have a need for an abstraction that allows you to think clearly about the problem. For application-level programming, the programming language is this abstraction, and if it’s not the right level abstraction for the problem then you’re using the wrong tool. You wouldn’t use assembly language for application-level programming, and IMO you shouldn’t use Go either.

          1. 2

            I mean…it’s just tooling? You wouldn’t say stop using Python because autocomplete is useful, or that C is inherently bad because someone uses GDB. Just because something has a pain point doesn’t mean it’s terrible.

            I’m definitely not a Go fanboy, but for the work projects I’m referring to it really is the right tool for the job. It was chosen for a reason. Besides, not everyone who writes Go gets to tell their employer that they are rewriting their codebase into something they like better. I’d rather those people be equipped w/ tooling to make their day pleasant and their software better :)

            1. 2

              All good points. I just feel like the readability of a language is pretty central, and it would be great if such a central aspect didn’t need a separate tool.

    38. 3

      S-expression syntax BUT you can ONLY make macros. There is a limited set of built in functions that cannot be expanded.

    39. 3

      If you implement retro emulators, compilers, reverse engineering, cool hacks, or security exploits you get upvotes. If you implement AI, cryptocurrency, or other trendy-in-media topics you get downvotes. The more votes you have, the faster it runs; if you have negative votes your code crashes.

    40. 3

      Locale- env dependent (and algol heavy) keywords so an LC_LOBSTERLANG=se gives you (a = b + c;) a är lika med b plus c semikolon – with support for the superior dialects, e.g. LC_LOBSTERLANG=se-skånska would work for: a aer lega mä be plos see sejmikolån.

    41. 3

      The ability to add two numbers, producing a third number being the arithmetic sum of the prior two numbers.

    42. 3

      Declarative state transitions, based on the layout of the file-system. Your app has a directory “start”, and from there you must declare all the states your app might encounter using one file for each state. The filename is the name of the state, the contents are how your app transitions from the current state to the next.

      Then you create a new directory with the same state name and add the sub-states to it.

      Loops are links to directories up in the tree. Function calls are links to leaf nodes. Transition from state a to b is a $ cd a b. Refactoring is done by $ mv and $ cp. Debugging is done by $ ls. Your LSP is $ find.

      No idea if it might work :D At least the states would be explicit!


             recur -> link to ../../read.lobster

      Exercise to the reader: implement fibonacci.

    43. 3

      Everything is inlined by default.

      If you don’t want that behavior, mark it “noinline”

      1. 8

        Except that “noinline” isn’t a word, so it’s called “outline” instead. You know, for clarity.

    44. 2

      Variable names always represent the address of the variable, as in BLISS. The contents of operator, a unary prefix period, must be used to fetch the contents of a variable.

      X = .Y + 2   // retrieve the value of Y and add 2
    45. 2

      All functions are generator functions that yields values with the claw keyword.

      Example (using the style of comments that @LenFalken suggested, and then also | and | for function definitions and function calls, to avoid confusing them with comments). Any other syntax than the claw feature is to be considered as pseudo-code, and is not part of my suggestion for LOBSTERS-LANG:

      ( count is a generator function )
      count || {
          for x in 0..99 { ( from 0 up to and including 99
              claw x         return x and wait             )
      a = count || ( get 0
      b = count ||   get 1 )
      ( print 2 up to and including 99 )
      for x in count || {
          print |x|
      ( add two numbers )
      add |x, y| {
          claw x + y
      1. 4

        Imagine the parsing nightmare that would be nested function calls using |.

        1. 4

          I think it’s doable, but it sure won’t make life any easier.

        2. 2

          It should be readable if the hypothetical lobster fmt code formatter was clever enough.

    46. 2

      Functions support Caddyshack call reference. For example:

      Hey(llama)! // Void return

      Hey(llama)! How ’bout something for the effort? // Something is a poorly written linked list modeled after first year computing science students work. The linked list contains the return values, along with Rodney Dangerfield quotes placed randomly.

      Bonus ransomware compiler:

      The compiler encrypts your output and you need to supply a cryptocurrency payment to decrypt it.

      Giant sky lobster: plz no smite

    47. 2

      Factories? Factories.

      1. 3


    48. 1

      Syntax so objects carry methods? No, syntax so functions can carry data! This feature makes lobsters-lang function-oriented, a.k.a. ‘objectional’.

    49. 1

      The only data type supported by the compiler is first-class functions. All others are implemented in user space via Church encoding.

      The standard library provides the usual literal syntax for numbers, strings, etc as macros, but all such values are still callable since they’re functions.

      Thus 5(4) makes total sense.