newbie question in elm function polymorphism

1,025 views
Skip to first unread message

Sunil S Nandihalli

unread,
Oct 28, 2014, 6:20:22 PM10/28/14
to elm-d...@googlegroups.com
Hi Everybody,
 How would I write a function which would have a polymorphic behaviour. I tried to lookup the code for asText in the language and it was using JS Native stuff. What I want to do is the following:

myToText: a->String

examples

myToText [1,2,3]  should return "The list is [1,2,3]"

myToText 212321 should return "The int is 212321"

myToText "hello"   should return "The String is hello"

How can I do this?

Thanks,
Sunil.

Dobes Vandermeer

unread,
Oct 28, 2014, 6:57:42 PM10/28/14
to elm-d...@googlegroups.com
Hi Sunil,

This kind of polymorphism (called overloading or ad-hoc polymorphism) isn't currently available in Elm.  If you had defined the original types yourself you might be able to do this, but currently in Elm you cannot make a single function that accepts either a number, list, or string.

What you can do, however, is wrap those types in a type of your own.  For example, instead of passing in the object itself, you can pass in a function that returns a string version of the object, or a record that has such a function in it.

Alternatively, you could define a union type with one variant for each type of thing your printing function supports and wrap the object in one of those before printing it.  Something like

data type MyPrintableThings = MyPrintableNumber number | MyPrintableList [a] | MyPrintableString String

myToText a = case a of
   | MyPrintableNumber n = ...
   | MyPrintableList x = ...
   | MyPrintableString s = ...

I hope that helps a bit ... the code above is not valid Elm code, just pseudocode.  The basic idea, though, is the same - you have to wrap the objects in something so they can all have the same type.



--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

John Mayer

unread,
Oct 28, 2014, 7:04:41 PM10/28/14
to elm-d...@googlegroups.com

To be clear, you can do this if you're willing to write a Native function and then give it a polymorphic type when wrapping/exposing it in the Elm level of your library. I had to do one or two of those tricks in elm-webgl. But it's relatively unsafe if you're not careful.

Jonathan Leonard

unread,
Oct 28, 2014, 7:22:22 PM10/28/14
to elm-d...@googlegroups.com
Can we interpret this question as yet another request for typeclasses? This is in fact a pretty perfect example.
I don't get the sense that the requestor would be turned off by the existence of the very thing he is requesting support for (in fact he'd probably be pleased by it :-)). I would also imagine that many more "OO programmers" (not that the requestor is necessarily an OO programmer) out there would like to have ad-hoc polymorphism as well.

--Jonathan

Evan Czaplicki

unread,
Oct 28, 2014, 7:27:25 PM10/28/14
to elm-d...@googlegroups.com
One way to do it is like this:

data Print
    = PrintInt Int
    | PrintString String
    | PrintList [Int]

run : Print -> String
run print =
    case print of
      PrintInt n -> "The int is " ++ show n
      PrintString s -> "The String is " ++ s
      PrintList ns -> "The int list is " ++ show ns

run (PrintInt 42)

That code should be good to go with 0.13 :) Does that suit your purpose?

OCaml has a cool feature to make this more light-weight, but Elm does not have that yet.

Meta-comment: I was curious how folks would answer here so I waited a bit :) Generally speaking, I'd avoid saying "you can't do this!" and go with "how about this way?" If they already know about ADTs and cannot be persuaded, then dive into other options. Always be aware of XY problems!

Jonathan Leonard

unread,
Oct 28, 2014, 7:58:17 PM10/28/14
to elm-d...@googlegroups.com
Seems to me like a lot of boilerplate to end up either:
a) calling 'show' and appending for non-strings (and you don't cover all possible non-strings here either-- noise only scales linearly with additional types needed to support).
or
b) appending for strings.

--Jonathan

Dobes Vandermeer

unread,
Oct 28, 2014, 8:02:33 PM10/28/14
to elm-d...@googlegroups.com

On Tue, Oct 28, 2014 at 4:27 PM, Evan Czaplicki <eva...@gmail.com> wrote:
OCaml has a cool feature to make this more light-weight, but Elm does not have that yet.

Evan,

Thanks for pointing out that feature of OCaml, it looks pretty cool ... basically a kind of open variants system.


Sunil S Nandihalli

unread,
Oct 28, 2014, 11:24:57 PM10/28/14
to elm-d...@googlegroups.com
Thanks Evan, that pattern helps. I can make do with this. I have read your comments for not having a richer typesystem. While I understand about the limited resources, added compiler-complexity and language-tooling recieving a higher priority. I don't think commercial-adoption would suffer due to the perceived difficulty of  understanding a richer typesystem.

P.S. Thanks for Elm. It has gotten me interested in UI programming again :)

Sunil S Nandihalli

unread,
Oct 28, 2014, 11:43:51 PM10/28/14
to elm-d...@googlegroups.com
Thanks Dobes. Evan had a similar answer.

Kim-Ee Yeoh

unread,
Oct 29, 2014, 2:21:51 AM10/29/14
to elm-d...@googlegroups.com
> data Print
> = PrintInt Int
> | PrintString String
> | PrintList [Int]
>
> run : Print -> String
> run print =
> case print of
> PrintInt n -> "The int is " ++ show n
> PrintString s -> "The String is " ++ s
> PrintList ns -> "The int list is " ++ show ns

This doubling of code, this introduction and immediate elimination of
the union / sum type is strikingly un-FP.

Let's trim off the fat to obtain:
print_Int 42
not
run (PrintInt 42)

And this:
print_String "hello"
not
run (PrintString "hello")

That's how to roll-your-own polymorphism for single-method typeclasses:

print_Int : Int -> String
print_String : String -> String

I recognize this as the final form of the explicit dictionary-passing-style.

(Ever get the feeling that "Scrap Your Typeclasses" gets tossed around
like a commonplace manifesto, doomed to be less read than mentioned
and even less applied than read?)

There's no type inference with this syntactic ad-hoc polymorphism. You
must specify the type by hand in the name of the function.


-- Kim-Ee

Jonathan Leonard

unread,
Oct 29, 2014, 3:55:56 PM10/29/14
to elm-d...@googlegroups.com

Meta-comment: I was curious how folks would answer here so I waited a bit :) Generally speaking, I'd avoid saying "you can't do this!" and go with "how about this way?" If they already know about ADTs and cannot be persuaded, then dive into other options. Always be aware of XY problems
 

Meta-Meta-comment: Also beware the YZ problem. Person A wants to do X using Y. Language designer hasn't implemented Y (and doesn't plan to implement Y) and offers Z (which is really just an instance of the 'human compiler' at work). Note that there are also U, V, & W ways to 'human compile' to something resembling Y. All of {U, V, W, Z} are very noisy pains in the bum (as is usually the case with human compilation) and Person A leaves in frustration.

:)

Raoul Duke

unread,
Oct 29, 2014, 3:58:10 PM10/29/14
to elm-discuss
+1 on both sides! :-) it is a tricky line to walk.

Paul Chiusano

unread,
Nov 6, 2014, 1:10:32 PM11/6/14
to elm-d...@googlegroups.com
I would just pass an additional argument that says how you want to render the name of the type. IMO, this is simpler than what some other folks have proposed.

myToText : String -> a -> String
myToText name a = "The " ++ name ++ " is " ++ show a

Or perhaps:

myToText : (a -> String) -> a -> String
myToText name a = "The " ++ name a ++ " is " ++ show a

If you like you can define: type Named a = a -> String, or even introduce a new data type for that.

In Elm, there is no way to inspect a polymorphic value and respond differently based on what type it is. If you want to do that sort of thing, just pass regular function arguments in to control the behavior. (You can also construct a new union type and pattern match on it like Evan mentioned, but I feel that is overkill here.)

As some other people have mentioned, there are some language features that make this sort of thing simpler and more convenient, but at the end of the day, they just amount to the compiler inserting these extra function arguments for you. But in the meantime, you can plumb these arguments around yourself to get what you want.

Hope this is helpful!

Paul :)
Reply all
Reply to author
Forward
0 new messages