1
import Html exposing (..)
import String

type alias Stack = List String


push : String -> Stack -> Stack
push tok stack = 
 (tok :: stack)


pop : Stack -> (Maybe String, Maybe Stack)
pop stack = 
 let 
   top = List.head stack 
 in 
 (top, List.tail stack)


reverseString: String -> String
reverseString incoming = 
 let 
   stringStack = incoming 
    |> String.split "" 
    |> List.foldl push [] 
 in 
   -- How to use pop() here?
   List.foldr String.append "" stringStack


main : Html 
main = 
 "Hello World!"
 |> reverseString
 |> toString 
 |> text 

I am attempting on my own reverse of a string using push() and pop(). I am able to incorporate push, but not able to use pop within the function reverseString. What am I doing wrong here?

lifebalance
  • 1,846
  • 3
  • 25
  • 57

3 Answers3

2

You are trying to List.foldr with your Stack ADT, but that's cheating; if Stack is really an ADT, we shouldn't be able to exploit that its a list!

List.foldr also matches the Stack ADT poorly, because it frees its function argument from handling the empty list, whereas the pop function forces us to treat both the case of the non-empty and empty stack.

If we want a Stack ADT, we'll have to do recursion through the stack manually, without using List.foldr. First, it'll be convenient to reduce the pop function to more concisely represent the two cases of "empty stack" and "non-empty stack":

pop : Stack -> Maybe (String, Stack)
pop stack = 
  case stack of 
    [] -> Nothing
    x :: xs -> Just (x, xs) 

Then we can formulate reverseString

reverseString : String -> String
reverseString string = 
  let
    loop stack = 
      case pop stack of 
        Nothing -> ""
        Just (symbol, stack') -> symbol ++ loop stack'
  in
    String.split "" string   -- Split the string into a list
    |> List.foldl push []    -- Push each letter on the stack
    |> loop                  -- Pop each letter off the stack 

It is perhaps easier to work directly with lists. Then Cons (::) is push and List.foldl is the function that reduces a stack by popping until empty.

reverseString2 : String -> String
reverseString2 = 
  String.split ""        -- "String -> Stack" (implicitly pushes)
  >> List.foldl (::) []  -- "Stack -> List" (reduces the stack)
  >> String.join ""      -- "List -> String" (nothing to do with stacks)
Søren Debois
  • 5,598
  • 26
  • 48
  • Thanks for an amazing and thorough answer! Any particular reason for use of the variable `stack'` with an apostrophe? – lifebalance Apr 30 '16 at 09:36
  • 1
    It's just to indicate it's not the same as the original `stack` argument. You don't need to, but I think it makes the code a little clearer. – Søren Debois Apr 30 '16 at 10:17
  • (If you like the answer, consider upvoting or accepting it. It makes it clear to others that they might find the answer useful, and it gives me the oh-so-satisfying little green box with a +10.) – Søren Debois Apr 30 '16 at 10:18
  • To avoid "cheating", how can `reverseString` be modified not to use `List.foldl`? – lifebalance Apr 30 '16 at 10:39
  • It can't, really: Conceptually, you need to push each letter in the input string onto the stack. For that, you need to iterate over the input string. I don't know that there's a way to do that in Elm without converting that String to a List, and using `List.foldl` to iterate over that list. I don't think its cheating, though: `List.foldl` conceptually iterates over the input string, calling `push` for each letter. This is what we want! – Søren Debois Apr 30 '16 at 11:11
  • How about use `peek()` to get the top value, and then `pop()` can be simplified to just return a stack? – lifebalance Apr 30 '16 at 11:18
  • Is the implementation of `pop` for stack ADT also equally "complex" in other functional languages (say Haskell or F#)? – lifebalance Apr 30 '16 at 11:22
  • ...and finally, will the cons/[uncons](http://package.elm-lang.org/packages/elm-lang/core/2.1.0/String#uncons) from `String` package help make things simpler? – lifebalance Apr 30 '16 at 13:12
  • 1
    Peek: sure; but you won't get out of having to distinguish between empty/non-empty stack (now in Peek). Same thing for other languages: you have decide what pop does on the empty stack. F# and Haskell have exceptions, so that's an option for the empty stack. I believe the Elm approach where you *have* to handle both cases explicitly leads to more robust programs. I'm not familiar with cons/incomes in String, but sure, they might make it possible to get a clearer implementation. – Søren Debois Apr 30 '16 at 15:30
1

Elm doesn't have iteration at the language level so you need to use a data structure that supports iteration and mapping. In this case, I think lazy list is likely the best as you won't blow the stack via recursion.

In this case, stackIterator yields a lazy list of string from a stack. In order to get the lazy sequence of values we want, we need a function that we can repeatedly apply to its own result, and since pop returns a tuple of head, stack, that function is ((mhd, tl) -> pop tl). The next parts operate as a pipeline, first pulling out the left part of the tuple, second promising to terminate the list if the returned stack top is Nothing, third, turning the list of Maybes into strings via Maybe.withDefault.

By just substituting LL.foldr and the lazy list, you have a proper non-recursive iteration on your stack involving your pop function.

A couple of style notes:

  • Your stack really wants String to be a type variable.
  • As a style choice, you should prefer pattern match to functions returning maybe on lists.
  • It makes the code cleaner if pop returns stack instead of maybe stack, since it can signal an empty stack via the maybe top result.
  • My own elm style isn't perfect. There's probably a clever way to compose snd and pop that doesn't require an explicit lambda binding.

    import Html exposing (..)
    import String
    import Lazy.List as LL exposing (LazyList)
    
    type alias Stack = List String
    
    
    push : String -> Stack -> Stack
    push tok stack = 
     (tok :: stack)
    
    
    pop : Stack -> (Maybe String, Stack)
    pop stack = 
     case stack of
       hd :: tl -> (Just hd, tl)
       _ -> (Nothing, [])
    
    stackIterator : Stack -> LazyList String
    stackIterator stack =
     LL.iterate (\(mhd, tl) -> pop tl) (pop stack)
      |> LL.map fst
      |> LL.takeWhile (\a -> a /= Nothing)
      |> LL.map (Maybe.withDefault "") -- Default just for theoretical completeness
    
    reverseString: String -> String
    reverseString incoming = 
     let 
      stringStack = incoming 
       |> String.split "" 
       |> List.foldl push [] 
     in 
       LL.foldr String.append "" (stackIterator stringStack)
    
    
    main : Html 
    main = 
     "Hello World!"
     |> reverseString
     |> toString 
     |> text 
    
lifebalance
  • 1,846
  • 3
  • 25
  • 57
Art Yerkes
  • 1,274
  • 9
  • 9
  • 1
    This will get rid of the lambda binding `LL.takeWhile ( flip (/=) Nothing)` but you may feel it does not buy you any extra clarity! – Simon H Apr 30 '16 at 11:36
0

FWIW, I modified Rundberget's version to use String as a Stack. While it is not as generic as the other solutions, it does not use List.foldr or List.foldl, and ends up being somewhat shorter.

module SStack where

import String

type alias SStack = String

empty : SStack
empty =
  ""


push : String -> SStack -> SStack
push tok stacks = 
  tok ++ stacks


pop : SStack -> Maybe (Char, SStack)
pop stacks = 
  String.uncons stacks


reverse : SStack -> SStack
reverse stack =
  case (pop stack) of 
    Nothing ->
      empty
    Just(x, r) -> 
      push  (reverse r) (String.fromChar x)

and here's the driver module:

import SStack as Stack exposing (..)
import Html exposing (..)

reverseString : String -> String
reverseString str =
  Stack.reverse str 


main : Html.Html
main =
  reverseString "Hello World !" 
    |> Html.text
lifebalance
  • 1,846
  • 3
  • 25
  • 57