Take a stand against long lines

Haskell, 3 bytes/line, 494 471 470 463 453 450 461 bytes

EDIT:

  • -26 bytes: Removed some redundant linebreaks and their associated comment markers, and changed -1+x into x-1.
  • +3 bytes: Oops, needed extra -- line after x-.
  • -1 byte: In f use c 47:[] instead of [c 47&0].
  • -7 bytes: Move newline handling to w.
  • -10 bytes: Inline a="Apple" and p="Pie" in # and use a dummy recursion for the 15 case.
  • -3 bytes: Inline w in f. Remove redundant -- between x and 15.
  • +11 bytes: Oops again! My string gap theory had a hole. Fixed by introducing % function. Finally made some automated testing to make sure there were no more surprises.

f takes an Int and returns a String.

{;f
n=
--
[--
1..
--
n--
]--
>>=
\
--
x->
--
gcd
x
15#
--
x++
--
c
47:
--
[--
]--
;1#
--
x=
--
n!!
--
(x-
--
1--
)--
;3#
--
_=
--
"A\
\p\
\p\
\l\
\e\
\"&
--
0--
;5#
--
_=
--
"P\
\i\
\e\
\"&
--
0--
;--
15#
--
_=
--
3#
--
0++
--
5#
--
0--
;n=
--
d++
--
[--
s++
--
t|
--
s<-
--
n--
,--
t<-
--
[c
9]:
--
d--
]--
;d=
--
(:
--
[--
]--
)--
<$>
--
[c
8..
--
c
0--
]--
;c
x=
--
[--
"9\
\"%
--
0--
,--
"8\
\"%
--
0..
--
]!!
--
x--
;--
[--
a]%
--
_=
--
a--
;x&
--
y=
--
x}

Try it online!

Test source restrictions! (Line 70 is excluded from the testing because removing its newline causes an infinite loop without output.)

Version with the most important squeezing tricks removed:

{;f n=[1..n]>>= \x->gcd x 15#x++c 47:[]
;1#x=n!!(x-1)
;3#_="Apple"
;5#_="Pie"
;15#_=3#0++5#0
;n=d++[s++t|s<-n,t<-[c 9]:d]
;d=(:[])<$>[c 8..c 0]
;c x=["9"%0,"8"%0..]!!x
;[a]%_=a
;x&y=x}

How it works

  • This code is written in the more rarely used indentation insensitive mode of Haskell, triggered e.g. by surrounding an entire program with {}. Since I'm actually defining a function rather than a whole program, I'm not quite sure how to count bytes; I've chosen to defensively count both the {}s and an extra ; declaration separator (the latter usually being a newline in normal Haskell mode.)
  • The main trick for making newlines "meaningful" is -- line comments, which make the next newline non-removable, and also a previous newline in the case when the previous line ends in an operator character (which is not itself part of a line comment).
  • The second trick is "string gaps", a sequence of whitespace between \ backslashes in string literals, indented for line continuations with possible indentation. A string gap with delimiters is removed from the parsed string.
    • If the newline of a string gap is removed, it becomes an added backslash in the string. For "Apple" and "Pie" this shows up directly in the output. For "8" and "9" a pattern match is used to give an error if the string has more than one character.
  • The third trick is the & and % operators, which allow forcing a line to end in an operator character for the first trick. We need this to end string literals, because \" is too wide to append --.
    • & is the general one, defined such that x&y=x.
    • % is defined such that [a]%y=a, allowing it to replace !!0 and simultaneously enforce that its string argument must have length 1.
  • The newline character poses a special problem, as \n seems impossible to fit in a string literal with only 3 bytes on the line.
    • Therefore, the more easily defined c x=["9"%0,"8"%0..]!!x is used to convert from an Int to a character, counting from the digit '9' downwards.
  • Because show is four characters, number output must be implemented by hand.
    • d is a list of the digit strings "1".."9".
    • n is an infinite list of number representations ["1","2","3",...] defined recursively using d.
  • # converts an Int x to its ApplePie form given an extra first argument that is the gcd of x with 15.

><>, 1 byte per line, 243 161 135 bytes

-26 bytes thanks to Jo King!

2D languages FTW! Although writing loops and branches using goto instructions instead of the 2D structure is not fun.

v
l
:
:
3
%
:
&
0
4
7
*
&
?
.
~
~
"
e
l
p
p
A
"
o
o
o
o
o
$
5
%
:
&
0
a
5
*
&
?
.
~
~
"
e
i
P
"
o
o
o
*
0
@
?
n
?
~
l
{
:
}
=
?
;
a
o
1

Try it online!, or watch it at the fish playground!

The fish swims downward along the code, using conditional gotos to skip things depending on what divides the accumulator.

I believe this meets the spec: whatever newlines are removed, the fish always hits the initial v (the only direction-changing instruction present), so the fish always swims downwards in the first column. Thus deleting a newline has the effect of simply removing the next character from the fish's path, and I don't think you can remove any of the characters without changing the output.


Jelly, 3 2 bytes/line, 106 80 56 bytes

“3
,e
5P
ḍ,
T⁾
ịi
⁾e
AF
ps
,5
⁾¤
pȯ
lµ
,€
⁾Y
”Ỵ
¢Z
¢F
¢v

Rows and columns of the string literal get transposed, so removing newlines messes up their order.

The remaining lines are separate links/functions and contain function calls (¢), so they can only be concatenated if the function calls are eliminated as well.

Try it online!