Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Not in Lisp! ("foo" "bar") and ("foobar") are lists of length 2 and 1, respectively.

(Python copies some bad ideas from C. Another one is having to import everything you use. It seems that since Python is written in C, its designer took it for granted that there will be something analogous to #include for using libraries, even standard ones that come with the language.)

Implicit string literal catenation is tempting to implement because it solves problems like:

   printf("long %s string"
          "nicely breaks up"
          "with indentation and all",
          arg, arg, ...)
and if you're working in a language which has comma separation everywhere, you can get away with it easily.

There are other ways to solve it. In TXR Lisp, I allow string literals to go across multiple lines with a backslash newline sequence. All contiguous unescaped whitespace adjacent to the backslash is eaten:

  This is the TXR Lisp interactive listener of TXR 273.
  Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet.
  TXR needs money, so even abnormal exits now go through the gift shop.
  1> "abcd \
      efg"
  "abcdefg"
If you want a significant space, you can backslash escape it; the exact placement is up to you:

  2> "abcd\ \
      efg"
  "abcd efg"
  3> "abcd    \
     \ efg"
  "abcd efg"
  4> "abcd    \ \
               efg"
  "abcd     efg"
  5> "abcd    \ \
     \         efg"
  "abcd              efg"


I like imports, it tells me what files symbols are coming from, even for built in libraries.

Maybe it is that through my work I use a half dozen languages, where it is hard to remember each in detail.

I have also worked on a javascript project where there were no imports/requires and the build process created one file. So you had to inspect the confusing build script to even know what was what.


I like the explicit nature of Python's imports.

And especially how I can choose the best way to indicate the sources of names in my code:

   import time
   t = time.perf_counter()

   import time, my_module
   t1 = time.perf_counter()
   t2 = my_module.perf_counter()

   from time import perf_counter as std_counter
   from my_module import perf_counter as my_counter
   t1 = std_counter()
   t2 = my_counter()

   try:
       from my_module import perf_counter
   except ImportError:
       # Fall back to standard implementation
       from time import perf_counter
   t = perf_counter()

   # import time as m  
   import my_module as m
   t = m.perf_counter()


> import perf_counter as my_counter

Yikes; you're renaming/aliasing global identifiers! Just no.


You could fairly easily work with a bunch of .js files that get catenated together by using an editor that can jump to a definition.

Build processes creating one file is the seven decade norm in computing.

Even if you literally don't catenate the .js files into one, they get loaded into one running image one way or another.


You mean

  long %s stringnicely breaks upwith indentation and all"
? In my experience, this always gets ugly when you want to insert spaces (= about always). Do you put them at the end or at the start of each string (apart from the first or last string)

I think scala’s mkString (https://superruzafa.github.io/visual-scala-reference/mkStrin...) is the best solution, visually, for such things, but unfortunately, it would require hackers in the parser to do the concatenation at compile time, where possible.

Scala’s multiline strings look nice, too, if you want to insert newlines, except for the stripMargin thing (https://docs.scala-lang.org/overviews/scala-book/two-notes-a...)


The spaces aren't the point of the comment; rather that we can break the literal into pieces and indent those pieces without affecting the contents. In a non-strawman real exmaple with real data, of course we include all the necessary spaces in the literals. However, this bug is easy to make in C; I've seen it numerous times.


That’s preciseLy my point. This looks nice, but it’s too easy to forget tone of those spaces and to hard to spot that.


I don't know of a good design that won't lead you to make errors when you don't want the spaces. You'd need some piece of syntax which indicates whether you want a space there or not. For instance, there could be a rule that a string literal ending in non-whitespace cannot joined with a literal starting with non-whitespace:

  "foo" "bar"     // error
  "foo " "bar"    // OK
  "foo" " bar"    // OK
  "foo" "" "bar"  // OK: "" doesn't start with non-whitespace, since it's empty
  "foo" " " "bar" // OK
The nice thing about this is that it's perfectly comatible with existing C.

All we have to do is to implement a compiler warning which detects when the rule is violated.

Users who implement it have to fix situations like "foo" "bar" into "foo" "" "bar".

Probably the rules should be smarter. Some kind of tokenization concept could be at play so that gluing together two letters or digits is bad, or two punctuation tokens, but letter/number and punctuation is okay.

  "foo" "1"     // error? OK?
  "foo" "bar"   // error
  "foo" ".bar"  // OK
  "1." "2" ".3" // OK
 
  "1." ".2"     // error: punct-punct


> Another one is having to import everything you use.

The alternative is what exactly? Have the entire standard library exposed at once? Make all modules create non-conflicting names for exported objects, so that the json parse function has to be called json_parse and the csv parse function has to be called csv_parse?

Seems less than ideal to me.


That's one way.

If these things are classes in a plain old single-dispatch oop system, you can havec a json-parser and csv-parser which have parse methods.

There could be packages/namespaces. So csv:parse and json:parse. These packages are standard and so they just exist; nothing to import.

In Python, you cannot use anything without an import! The top-level modules (which serve as de facto namespaces) themselves are not visible.

Say there is a csv module with a parse. You cannot just do:

  csv.parse(...)
you have to first say

  import csv
This jaw-droppingly moronic.


It lets you debug. E.g. if they have made a file called cvs.py in the same directory, then print (cvs.__file__) will show you this. If they have some weirdly screwed up paths with multiple pythons installed and multiple copies of the modules etc., same.

I will not Go lang has the same feature carried forward from C. It helps a lot in the reading code side of the code lifecycle. And Go compiler makes you keep the imports up to date, which is good.


> It lets you debug.

It lets you debug Python problems which the system created in the first place.

> If they have some weirdly screwed up paths with multiple pythons installed and multiple copies of the modules etc., same.

Doesn't happen in a sane language. Or, even not a sanely defined language/implementation.

I can easily have multiple different GCC copies (possibly for different processor targets) on the same machine. Each one knows where its own files are; an #include <stdio.h> compiled with your /path/to/arm-linux-eabi-gcc will positively not use your /usr/include/stdio.h, unless you explicitly do stupid things, like -I/usr/include on the command line.


You might like [Hissp's][1] import system. It does compile down to Python.

[1]: https://github.com/gilch/hissp


> This jaw-droppingly moronic.

It can be slightly inconvenient but doesn’t feel moronic to me. It means that except for the built-in functions, everything can be traced to either a definition or an import. Makes tracking code much easier.


Why not import the built-in functions too? The only thing not requiring import can be import.

  from python import def  # now you can def
That should be even easier to track things; now you don't have to deal with the difficulty of def not being defined anywhere in your code. It's traced to an import, which is telling you that def comes from python, liberating you from having to know that and remember it.


"def" is not a function. It's not even an identifier.


This exercise requires you to imagine a somewhat different Python in which you can (and must) do from python import def if you are to use def.


I mean, that would be Lisp, and in that context I wouldn't really have a problem with it.


   @"
  here strings in PS are fine for this purpose and 
   even allows whitespace anywhere            
    but because of the latter you can't indent it    
     with your other code   
 "@ -split "`r`n" | % {'<SOL>{0}<EOL>' -f $_ }
 <SOL>    here strings in PS are fine for this purpose and <EOL>
 <SOL>     even allows whitespace anywhere            <EOL>
 <SOL>      but because of the latter you can't indent it    <EOL>
 <SOL>       with your other code   <EOL>


I posted a Unix StackExchange answer with some tricks for doing this in shell programming, very similar to your <SOL> trick.

https://unix.stackexchange.com/questions/76481/cant-indent-h...


Having everything be imported is what makes the language be useable. Especially if you never import * you can easily find the definition and meaning of everything you read on the screen. A prime example of explicit is better than implicit.

And backslash doesn’t let you have the literal obey the proper indenting. Might as well use “””


> you can easily find the definition and meaning of everything you read on the screen

I don't want to be finding definitions of things that the language provides in the code.

Languages that don't work this way have IDE's, editor plug-ins or other tools for easily finding the definitions of things that are in the language, without hunting for them through intermediate definition steps in the same file.

"I've spent all my life in and out of jails, so I expect bars on doors and windows ..."


I'm gonna disagree on the import thing. Compared to Ruby where requires are magic bags of metaprogramming bullshit, Python is much much easier to reason about. It takes some getting used to that require 'json' actually adds methods to existing classes.


"require 'json'" is just another #include in disguise, and if it monkey patches existing classes, it ... probably should not exist in any form.

If the language supports json, it should just do that.

  1> #J[1,2,3]
  #(1.0 2.0 3.0)
  2> (get-json "[1,2,3,{\"foo\":true}]")
  #(1.0 2.0 3.0 #H(() ("foo" t)))
  3> (put-json #(1.0 2.0 t))
  [1,2,true]t


Welcome to Ruby.

    $ irb
    irb(main):001:0> { hello: "world" }.to_json
    NoMethodError (undefined method `to_json' for {:hello=>"world"}:Hash)

    irb(main):002:0> require 'json'
    irb(main):003:0> { hello: "world" }.to_json
    => "{\"hello\":\"world\"}"


I mean, I understand that classes which are open to extension with new methods is useful, and the right way to do OOP and all.

If it was CLOS with multiple dispatch, it would be easier to swallow. Because it would look like:

   (to-json { hello: "world" })
   ;; error: no such function!
Then load the module, and you have a generic to-json function now, with a method specialized to handle the dictionary object and all. (I still wouldn't want to be doing this if it's supposed to be a language built-in).

I regard the ability to add new methods to a class as good, but with a valid use case, like extending some third party piece with new methods in your own application. And the fact of not having to declare methods in a class definition, which is cumbersome. Just write a new method in that class's file, at the bottom, and there it is.

I ideally don't want that third-party piece itself to be divided into three pieces that I have to separately load to get all of the methods. Or worse, pieces from separate third parties that add methods to each other.

I copied a thing or two from Ruby in TXR Lisp. The object system as a derived hook, and that was inspired by something in Ruby:

  1> (defstruct foo ()
       (:function derived (super sub) (prinl `derived @super @sub`)))
  #<struct-type foo>
  2> (defstruct bar foo)
  "derived #<struct-type foo> #<struct-type bar>"
  #<struct-type bar>
  3> (defstruct xyzzy bar)
  "derived #<struct-type bar> #<struct-type xyzzy>"
  #<struct-type xyzzy>
The derived hook is inherited (like any other static slot), so it fires in bar also. The function can distinguish which class is being derived by the super argument.


The difference is: in C, it's pretty unlikely someone wants to add strings. I suppose it's even illegal in the later C versions.


It is positively not illegal in any standard verision of C since ANSI C 89.

It's an essential feature used in all sorts of everyday code.

C99 added printf conversion specifiers that are hidden behind macros, and idomatic usage of them relies on string catenation.

  uint32_t x = 0;

  printf("x = " PRIx32 "\n", x);
where PRIx32 might expand to "%lx" (if uint32_t is the same as unsigned long in that compiler).

All sorts of C macrology relies on string catenation. Kernel print messages:

  printk(KERN_EMERG "%s: temperature sensor indicates fire!", dev->name);
                   ^ must not have comma here


Interesting. Arguably tho this shows how C is aging. I find that PRIx32 a bit ugly.

Although I just had a (logging) use case in go where I missed cpp macros - wanted the log statement to get something from the file and just had to pass it in as another parameter.


I have also never used PRI-anything. It's a crime against readability.

If I have a uint32_t which needs printing I cast it to (unsigned long) and use %lu or %lx. This requires more typing in the argument list, but keeps the format string tidy. It's important for the format string to be tidy, because that's the reason of its existence: to clearly and concisely convey the shape of what is being printed.


I know that. I meant that “abc” + “def” is most likely illegal (although “abc” + ‘d’ is not).


> I meant that “abc” + “def” is most likely illegal

That would be adding 2 pointers, and that's indeed illegal.

However, you can subtract them: “abc” - “def” . Now, the result is not a pointer any more, it's a ptrdiff_t (an integer type), so most compilers will warn if you try to assign that to a char *.


You started talking about "adding strings" in a thread about adjacent literals, without mentioning any + operator..

String catenation ("adding") by adjacency (no visible operator) is a thing; "add" doesn't imply that we are talking about a + operator:

  $ awk 'BEGIN { x = "abc-" 2 + 2 "-def"; print x}'
  abc-4-def


Because the parent compared Python's behavior to that of C. The difference of course is that adding strings doesn't make sense in C, so there's no danger of misinterpreting "abc" "def" in C, as there is in Python.


The same comma typo bug could happen in C.

  execl("/bin/sh", "/bin/sh", "-c"
        "echo foo", (char *) NULL);
Here we get one "-cecho foo" argument passed to the shell instead of two, so it can't work.

Initializers are another example:

  char *strArray[] = {
    "how", "now"
    "brown", "cow"
  };
In non-variadic function and macro calls, you will most likely get an insufficient arguments error, unless another mistake compensates for that.


The Python certainly looks nicer though.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: