HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Haskell's intersperse in D

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
interspersehaskellstackoverflow

Problem

I am learning D. I'm trying to implement Haskell's intersperse in D. I want to use ranges. I feel that the intermediate "interspersed" value is wrong. Additionally, I suspect the concatenation character by character is inefficient. What is a more idiomatic way to implement this function, please?
string translateToSwedish(immutable string english) pure
{
string interspersed;
foreach(char c; english)
interspersed = interspersed ~ c ~ 'f';
return interspersed;
}

void main()
{
assert(translateToSwedish("Hello") == "Hfeflflfof");
}


My initial thought was to try std.range.roundRobin with repeat:
string translateToSwedish(immutable string english) pure
{
import std.range;
return roundRobin("f".repeat(), english);
}


This however is wrong for at least two reasons:

-
roundRobin is eager and will evaluate the full range even if another range finishes first. Repeat constructs an infinite range, thus my translateToSwedish function is also infinite.

-
This code doesn't typecheck:

main.d(6): Error: cannot implicitly convert expression (roundRobin(repeat("f"), english)) of type Result to string

Solution

Its unfortunate that roundRobin doesn't work here, as it sounds just right for the task. Some functions (e.g. std.range.zip) have a StoppingStrategy that allows you to determine whether to stop at the shortest or longest range, and I'm not sure why roundRobin doesn't support this.

As @sigod pointed out, the issue with roundRobin was just a simple typo (I missed it too!).
It is probably the best option, but I'll leave my alternative anyways:

import std.range, std.algorithm;

auto translateToSwedish(immutable string english) pure 
{
    return english.map!(c => only(c, 'f')).joiner;
}

void main()
{
    assert(translateToSwedish("Hello").equal("Hfeflflfof"));
}


map replaces each character c with the sequence c'f'.
joiner links these pairs of character into a single range.

Note the use of the auto return type. Returning a string would imply eagerly committing the result within translateToSwedish. However, we don't know what the user of translateToSwedish will do with the result; if they are just passing it to another range-manipulating-function, they'd much rather we give them a range.

You'll notice many D functions are written like this. You generally want to keep passing around ranges until it is absolutely necessary to evaluate the result.

With that in mind, lets allow translateToSwedish to operate on a range as well:

import std.traits, std.range, std.algorithm;

auto translateToSwedish(R)(R english) pure if (isSomeString!R)
{
    return english.map!(c => only(c, 'f')).joiner;
}

void main()
{
    assert(translateToSwedish("Hello").equal("Hfeflflfof"));
}


Now translateToSwedish is well equipped to be used in a lazily evaluated function chain:

auto result = "Hello"
    .translateToSwedish
    .translateToGerman
    .translateToPigLatin
    // and so on

Code Snippets

import std.range, std.algorithm;

auto translateToSwedish(immutable string english) pure 
{
    return english.map!(c => only(c, 'f')).joiner;
}

void main()
{
    assert(translateToSwedish("Hello").equal("Hfeflflfof"));
}
import std.traits, std.range, std.algorithm;

auto translateToSwedish(R)(R english) pure if (isSomeString!R)
{
    return english.map!(c => only(c, 'f')).joiner;
}

void main()
{
    assert(translateToSwedish("Hello").equal("Hfeflflfof"));
}
auto result = "Hello"
    .translateToSwedish
    .translateToGerman
    .translateToPigLatin
    // and so on

Context

StackExchange Code Review Q#105929, answer score: 3

Revisions (0)

No revisions yet.