Search code examples
swifthaskellfunctional-programmingopaque-types

How can I translate Opaque types into Haskell?


I took the following code from

Apple Inc.. (2022). "Opaque Types — The Swift Programming Language (Swift 5.7)"

Swift documentation example:

// main :: IO ()
func main() -> () {
       let shape = join(
               Triangle(size: 3),
               flip(Triangle(size: 3))
       )
       return print(
               shape.draw()
       )
}

protocol Shape {
       func draw() -> String
}

func flip<T: Shape>(_ shape: T) -> some Shape {
       return FlippedShape(shape: shape)
}

func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
       JoinedShape(top: top, bottom: bottom)
}

struct FlippedShape<T: Shape>: Shape {
       var shape: T
       func draw() -> String {
               let lines = shape.draw().split(separator: "\n")
               return lines.reversed().joined(separator: "\n")
       }
}

struct JoinedShape<T: Shape, U: Shape>: Shape {
       var top: T
       var bottom: U
       func draw() -> String {
               return top.draw() + "\n" + bottom.draw()
       }
}

struct Triangle: Shape {
       var size: Int
       func draw() -> String {
               var result: [String] = []
               for length in 1...size {
                       result.append(String(repeating: "*", count: length))
               }
               return result.joined(separator: "\n")
       }
}

main()

I am wondering how can I translate it to Haskell. The end goal is joining a Triangle datatype with the flipped version of a Triangle datatype. This is my attempt:

Haskell example:

module Main where

import Data.List.Extra

class Shape a where
 draw :: a -> String

newtype Triangle = Triangle Int
newtype Square = Square Int

instance Shape Triangle where
 draw (Triangle n) = unlines $ take n $ iterate ('*' :) "*"

flip :: Shape a => a -> a
flip shape = flippedShape shape

join :: Shape a => a -> a -> a
join top bottom = joinedShape top bottom

flippedShape :: Shape a => a -> a
flippedShape = unlines . reverse . splitOn "\n" . draw

joinedShape :: Shape a => a -> a -> a
joinedShape top bottom = draw top <> "\n" <> draw bottom

main :: IO ()
main = print $ Main.join (Triangle 3) (Main.flip (Triangle 3))

So, I have two questions:

  • How can I translate the Swift example into Haskell ?, and
  • What's the Haskell equivalent for some (Opaque type) ?

Solution

  • The most direct translation adds a shape data type to supplement the shape type class.

    class Shape a where draw :: a -> String
    
    newtype SomeShape = SomeShape String
    -- if desired, can make this instance:
    instance Shape SomeShape where draw (SomeShape s) = s
    
    flip :: Shape a => a -> SomeShape
    flip s = SomeShape (unlines . map reverse . lines $ draw s)
    

    This case is a bit too simple to really capture all the exciting bits of opaque types, but because of that simplicity I would also consider using String directly (or a difference-list equivalent if efficiency is a concern), with no type class or additional data type.

    type Shape = String
    
    triangle :: Int -> String
    triangle n = {- ... -}
    
    flip :: Shape -> Shape
    flip = unlines . map reverse . lines
    
    joinVertical :: Shape -> Shape -> Shape
    joinVertical s1 s2 = s1 ++ "\n" ++ s2