loading...

[Challenge] log("this") or log("this").withData({})

nombrekeff profile image Manolo Edge ・1 min read

This challenge is intended for JavaScript, but you can complete it with any language you like and can.


Fun little challenge. Hope you enjoy it 😄

Challenge Description

You need to create a function called log that receives a string and prints the string prepended with "log:". Easy.

Wait, log also returns an object with one method withData, which receives any data. If withData is called, it should print the message passed to log, prepended with "withData:" and appended with the data.

Check the following scenarios to understand the challenge better.

Scenario 1

If we call just log("test"); a message should be logged containing the message "log: test".

log("test"); // > log: test

Scenario 2

But if we call the method withData as follows log("test").withData({ user: 'keff' });; It should log only the message "withData: test { user: 'keff' }".

See how the message from just log("test") is not printed. This is because the logic from log() is ignored when we call .withData().

log("test").withData(2); // > withData: test 2

Can you accomplish this behavior?

💪 Best of luck! 💪


Posted on by:

nombrekeff profile

Manolo Edge

@nombrekeff

I don't know... I just code and they pay me!

Discussion

markdown guide
 

This is possible in Python by utilizing the destructor and relying on the fact that Python uses reference counting as its first GC tier:

class log:  # yes this is a class but you can just use it like a function
    def __init__(self, text):
        self.text = text
        self.print_on_dtor = True

    def __del__(self):
        if self.print_on_dtor:
            self.print_on_dtor = False
            print('log:', self.text)

    def with_data(self, data):
        self.print_on_dtor = False
        print('withData:', self.text, data)

JS doesn't have destructors so you can't use that trick there. I'm also not sure if the GC is as predictable as Python's (and with all the various implementations I won't be surprised if it has different rules on different engines...)

 

Thanks for another fun challenge!

This took quite a bit of thinking, but I ended up working with the fact that setTimeout isn't actual asynchronous.
So here's what my log function looks like:

function log(text) {
    let logger = {
        label: "Log:",
        message: [ text ],
        withData(...data) {
            this.label = "withData:";
            this.message.push(...data);
        }
    };

    setTimeout(() => console.log(logger.label, ...logger.message), 0);

    return logger;
}

And it passes the tests, of course!

log("test"); // > log: test
log("test").withData(2); // > withData: test 2

It can even handle more complicated synchronous behavior!

let logger = log("Here's a");

if (Math.random() > 0.5) {
    logger.withData("number", Math.random());
}
else {
    logger.withData("boolean", Math.random() > 0.5);
}
 

Problem with your solution is the setTimeout and how it schedules the task to be executed - there are no guarantees when the task will be executed and the execution depends on number of task waiting in the execution queue.

In this scenario, what will be printed out first?

    log('test1')
    log('test2').withData('test2')
    console.log('test')

It will print out the last 'test' first.

 

Yup nice point, I could only figure this one out with setTimeout myself. This was intended as a fun experiment for people. Maybe to understand setTimeout or perhaps finding some quirks in the language, hopefully, if not, just a weird little behavior nonetheless.

 

I'm glad you enjoyed it :) cool solution. More extensible than mine for sure.

 

I don't think this is really possible in vanilla JS.

 

I guess. The only way I figured it out was making use of setTimeout, but, as you explained in another comment, it's not 100% guaranteed it will always work and can behave in unexpected ways.

Hopefully, somebody comes up with some weird solutions :)