繁体   English   中英

如何根据字符串解析的结果在Haskell中返回多态类型?

[英]How to return a polymorphic type in Haskell based on the results of string parsing?

TL; DR:
如何在返回类型中编写多态的函数? 我正在进行一项练习,其任务是编写一个能够分析String的函数,并根据其内容生成Vector [Int]Vector [Char]Vector [String]

更长的版本:
以下是一些预期函数的行为示例:

  • 字符串"1 2\\n3 4"将生成一个Vector [Int] ,它由两个列表组成: [1,2][3,4]

  • 字符串"'t' 'i' 'c'\\n't' 'a' 'c'\\n't' 'o' 'e'"将生成一个Vector [Char] (即由列表组成) "tic""tac""toe" )。

  • 字符串"\\"hello\\" \\"world\\"\\n\\"monad\\" \\"party\\""将生成一个Vector [String] (即["hello","world"]["monad","party"] )。

错误检查/异常处理不是此特定练习的关注点。 在这个阶段,所有测试都是纯粹完成的,也就是说,这不是IO monad的领域。

到目前为止我所拥有的:

我有一个函数(和新的数据类型),它能够对字符串进行分类。 我还有函数(每个IntCharString ),它可以将字符串转换为必要的Vector。

我尝试过的:

  • (如果我将三个转换函数填充到单个函数中(即,使用case..of结构在字符串的case..of进行模式匹配), VectorType进行类型检查。

  • 我尝试制作一个Vectorable类,并为每种类型定义一个单独的实例; 我很快意识到这种方法只有在函数的参数因类型而异时才有效。 在我们的例子中,参数的类型不会改变(即,它总是一个String )。

我的代码:

一些评论

  • 解析: mySplitter对象和mySplit函数处理解析。 无可否认,这是一个基于Splitter类型和Data.List.Split.Internalssplit函数的粗略解析器。

  • 分类: classify函数能够根据字符串确定最终的VectorType

  • 转换: toVectorNumbertoVectorChartoVectorString函数能够将字符串分别转换为Vector [Int]Vector [Char]Vector [String]

  • 作为旁注,我正在根据导师的推荐尝试CorePrelude 这就是为什么你会看到我使用普通Prelude函数的通用版本。

码:

import qualified Prelude
import CorePrelude                   

import Data.Foldable (concat, elem, any)
import Control.Monad (mfilter)
import Text.Read (read)
import Data.Char (isAlpha, isSpace)

import Data.List.Split (split)
import Data.List.Split.Internals (Splitter(..), DelimPolicy(..), CondensePolicy(..), EndPolicy(..), Delimiter(..))

import Data.Vector ()                       
import qualified Data.Vector as V           

data VectorType = Number | Character | TextString deriving (Show)

mySplitter :: [Char] -> Splitter Char
mySplitter elts = Splitter { delimiter        = Delimiter [(`elem` elts)]
                           , delimPolicy      = Drop
                           , condensePolicy   = Condense
                           , initBlankPolicy  = DropBlank
                           , finalBlankPolicy = DropBlank }

mySplit :: [Char]-> [Char]-> [[Char]]
mySplit delims = split (mySplitter delims)           

classify :: String -> VectorType
classify xs
  | '\"' `elem` cs = TextString
  | hasAlpha cs = Character
  | otherwise = Number
  where
    cs = concat $ split (mySplitter "\n") xs
    hasAlpha = any isAlpha . mfilter (/=' ')

toRows :: [Char] -> [[Char]]
toRows = mySplit "\n"

toVectorChar ::    [Char] -> Vector [Char]
toVectorChar =   let toChar = concat . mySplit " \'" 
                 in V.fromList . fmap (toChar) . toRows

toVectorNumber  :: [Char] -> Vector [Int]
toVectorNumber = let toNumber = fmap (\x -> read x :: Int) . mySplit " "
                 in  V.fromList . fmap toNumber . toRows

toVectorString  :: [Char] -> Vector [[Char]]
toVectorString = let toString = mfilter (/= " ") . mySplit "\""
                 in  V.fromList . fmap toString . toRows

你不能。

Haskell不支持协变多态,如果是的话就不会有用。


基本上所有人都可以回答这个问题。 现在为什么会这样。

像OO语言一样“返回多态值”并不好,因为返回任何值的唯一原因是在其他函数中使用它 现在,在OO语言中,你没有函数, 只有对象附带的方法,所以“返回不同类型”很容易:每个都有内置的合适方法,并且每个实例都可以变化。 (这是一个好主意是另一个问题。)

但在Haskell中,功能来自其他地方。 他们不知道特定实例的实现更改,因此可以安全地定义这些函数的唯一方法是了解每个可能的实现 但是如果你的返回类型真的是多态的,那是不可能的,因为多态是一个“开放”的概念(它允许随后添加新的实现变种)。

相反,Haskell有一个非常方便且完全安全的机制来描述一组封闭的“实例” - 你实际上已经自己使用它了! 抽象数据类型。

data PolyVector = NumbersVector (Vector [Int])
                | CharsVector (Vector [Char])
                | StringsVector (Vector [String])

这是你想要的返回类型。 这个函数不会是多态的,它只会返回一个更通用的类型。


如果你坚持认为它应该是多态的

现在...... 实际上 ,Haskell确实有办法对“多态返回”进行排序。 在OO中,当您声明返回指定类的子类时。 好吧,你不能在Haskell中“返回一个类”,你只能返回类型。 但是那些可以用来表达“......的任何实例”。 它被称为存在量化

{-# LANGUAGE GADTs #-}

data PolyVector' where
  PolyVector :: YourVElemClass e => Vector [e] -> PolyVector'

class YourVElemClass where
  ...?
instance YourVElemClass Int
instance YourVElemClass Char
instance YourVElemClass String

我不知道这对你来说是否有点吸引力。 事实是,它更复杂,使用起来更加困难; 你不仅可以直接获得任何可能的结果,而且只能通过YourVElemClass方法来使用这些元素。 GADT在某些应用中可能非常有用,但这些通常涉及具有非常深刻的数学动机的类。 YourVElemClass似乎没有这样的动机,因此,与存在量化相比,使用简单的ADT替代方案会更好。

Luke Palmer一个着名的反对存在的咆哮 (注意他使用另一种语法,存在性特定的,我认为它是过时的,因为GADT更严格一般)。

简单,使用总和类型!

data ParsedVector = NumberVector (Vector [Int]) | CharacterVector (Vector [Char]) | TextString (Vector [String]) deriving (Show)

parse :: [Char] -> ParsedVector
parse cs = case classify cs of
  Number     -> NumberVector $ toVectorNumber cs
  Character  -> CharacterVector $ toVectorChar cs
  TextString -> TextStringVector $ toVectorString cs

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM