简体   繁体   English

有没有办法隐式地将一个类型强制转换为 haskell 中的另一个包装类型?

[英]Is there a way to implicitly coerce a type to another wrapper type in haskell?

I'm coding a simple function in Haskell for my university assignment - the function should take an integer n and make an ascii triangle with the bottom row being 2*n + 1 wide. I'm coding a simple function in Haskell for my university assignment - the function should take an integer n and make an ascii triangle with the bottom row being 2*n + 1 wide.

This is my implementation:这是我的实现:

module Triangle where

data TArg = CallArg Int | RecursiveArg (Int, Int)
triangle :: TArg -> String

triangle (CallArg k) = triangle (RecursiveArg (k, k))
triangle (RecursiveArg (0, _)) = ""
triangle (RecursiveArg (k, q)) = triangle (RecursiveArg (k-1, q+1)) ++ spaces ++ stars ++ spaces ++ ['\n'] where
  spaces = replicate q ' '
  stars = replicate (k*2 - 1) '*'

Now, while it works - I don't like the necessity to specify the variant when calling the function, and would rather have some sort of coercion or a different way to specify two possible variations.现在,虽然它有效 - 我不喜欢在调用 function 时指定变体的必要性,并且宁愿使用某种强制或不同的方式来指定两种可能的变体。

Thank you in advance先感谢您

It looks like you're trying to set up a recursive function with a call wrapper because the recursive call requires a second argument.看起来您正在尝试使用调用包装器设置递归 function,因为递归调用需要第二个参数。 I don't think your approach of encapsulating the arguments in a data structure is a good one.我认为您将 arguments 封装在数据结构中的方法不是一个好的方法。 A more common approach is to create two functions, an outer function that you call from outside the definition, and an inner function to call recursively.更常见的方法是创建两个函数,一个是从定义外部调用的外部 function,另一个是递归调用的内部 function。 By using scoping, you can hide the recursive inner function from the outside world.通过使用范围,您可以向外界隐藏递归内部 function。 To acheive this, I would rewrite your code as follows为了实现这一点,我将重写您的代码如下

module Triangle where

triangle :: Int -> String
triangle k = triangle' k k where
  triangle' :: Int -> Int -> String
  triangle' 0 _ = ""
  triangle' k q = 
    triangle' (k - 1) (q + 1) ++ spaces ++ stars ++ spaces ++ "\n" where
      spaces = replicate q ' '
      stars  = replicate (k * 2 - 1) '*'

Andrew Ray's answer is what is normally done to write a function that needs to use a different call signature in its recursive form than it does in its initial invocation. Andrew Ray 的回答是编写 function 通常需要在其递归形式中使用与在其初始调用中不同的调用签名。

But to complement that answer, what would you do if triangle had to exist as it is shown in the OP?但是为了补充这个答案,如果triangle必须存在,如 OP 中所示,你会怎么做? 1 1

If you always need to call it as triangle (CallArg x) for some x and wish you could just call triangle x instead, simply make your own wrapper function:如果您总是需要将某些x称为triangle (CallArg x)并希望您可以只调用triangle x ,只需制作自己的包装器 function:

triangle' x = triangle (CallArg x)

-- or
triangle' =  triangle . CallArg

Now the original triangle gets to have its signature they way it likes ( TArg -> String ), and you get to call a function with the signature you wish it had ( Int -> String ).现在,原始triangle得到了它喜欢的签名( TArg -> String ),你可以用你希望的签名( Int -> String )调用 function 。 Everybod wins.每个人都赢了。 And the wrapper is trivial, so even on a pure character-count basis using the wrapper is shorter once you need to call triangle' more than a handful of times.并且包装器是微不足道的,所以即使在纯字符计数的基础上,一旦您需要多次调用triangle' ,使用包装器的时间也会更短。

If you wish to avoid the very tiny cost of having to call a different name than triangle , and this is a more realistic program where triangle is something you import from a different module, you can define your own wrapper module instead:如果您希望避免调用与triangle不同的名称而产生的非常小的成本,并且这是一个更现实的程序,其中triangle是您从不同模块导入的东西,您可以定义自己的包装器模块:

module MyTriangle ( triangle )
where

import qualified Original.Triangle as OT

triangle = OT.triangle . OT.TArg

Now the rest of your code only needs to change the import from import Original.Triangle ( triangle ) to import MyTriangle ( triangle ) , and all the (presumeably many, if this is worth bothering with) places where you call triangle get to use the name and signature you prefer.现在,您的代码的 rest 只需将导入import Original.Triangle ( triangle )更改为import MyTriangle ( triangle ) ,并且您调用triangle的所有(可能很多,如果这值得打扰的话)的地方都可以使用您喜欢的姓名签名。

To generalise: any time a function you want to use several times seems like it has a more complex signature than you actually need because the extra structure and/or values it takes can be mechanically derived from what you want to give it, you can simply create a wrapper that does the translation.概括地说:任何时候你想多次使用 function 似乎它的签名比你实际需要的更复杂,因为它所需要的额外结构和/或值可以从你想要给它的东西中机械地推导出来,你可以简单地创建一个进行翻译的包装器。 You don't need the language to "implicitly coerce" anything;你不需要语言来“隐式强制”任何东西; you can say explicitly (but only in one place) how you want it to "coerce", give that a name, and then get the behaviour you want merely by using the name.你可以明确地(但只能在一个地方)说出你希望它如何“强制”,给它一个名字,然后仅仅通过使用这个名字来获得你想要的行为。 That way you get concise code, and you avoid redundancy, and everything that's going on is explicit.这样你就可以得到简洁的代码避免冗余,并且所有发生的事情都是明确的。

(The heart of the technique shown in Andrew Ray's answer is in fact is exactly this "translation wrapper" idea, just also moving the original function to be purely an internal detail if nobody external will ever need to call anything but the wrapper) (Andrew Ray 的回答中显示的技术的核心实际上正是这种“翻译包装器”的想法,如果没有外部人员需要调用任何东西,除了包装器,也将原始 function 移动为纯粹的内部细节)


1 Say, because it's a third-party function you're just calling, and/or it's actually called sometimes with the case of the data structure, or whatever the reason. 1说,因为它是第三方 function 你只是在调用,和/或它有时实际上是在数据结构的情况下调用的,或者其他任何原因。

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

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